算法竞赛入门——尺取法(双指针)

Subsequence

Description

A sequence of N positive integers (10 < N < 100 000), each of them less than or equal 10000, and a positive integer S (S < 100 000 000) are given. Write a program to find the minimal length of the subsequence of consecutive elements of the sequence, the sum of which is greater than or equal to S.

Input

The first line is the number of test cases. For each test case the program has to read the numbers N and S, separated by an interval, from the first line. The numbers of the sequence are given in the second line of the test case, separated by intervals. The input will finish with the end of file.

Output

For each the case the program has to print the result on separate line of the output file.if no answer, print 0.
Sample Input

Sample Input

2
10 15
5 1 3 5 10 7 4 9 2 8
5 11
1 2 3 4 5

Sample Output

2
3

题目大意

给定长度为n的数列整数以及整数S。求总和不小于S的连续子序列的最小长度。如果不存在这样的子序列,输出0

分析

思路一:
先预处理,求前缀和sum,再用二分搜索确定使序列和不小于S的结尾的最小值

思路二:
用尺取法,定义两个变量s,t,使它们分别指向序列的头和尾,从前到后寻找满足条件的子序列,并更新最短的连续子序列长度

样例对应的区间变化如下
在这里插入图片描述

AC代码

思路一

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N=100010;
int a[N];
int sum[N];

int main()
{
    
    
	int x;
	scanf("%d",&x);
	int n,S;
	while(x--)
	{
    
    
		scanf("%d %d",&n,&S);
		for(int i=0;i<n;i++)
		{
    
    
			scanf("%d",&a[i]);
		}
		for(int i=0;i<n;i++)   //求前缀和
		{
    
    
			sum[i+1]=sum[i]+a[i];
		}
		if(sum[n]<S)   //无解情况
		{
    
    
			printf("0\n");
		}
		else
		{
    
    
			int res=n;
			for(int s=0;sum[s]+S<=sum[n];s++)    //注意循环结束条件
			{
    
    
				int t=lower_bound(sum+s,sum+n,sum[s]+S)-sum;
				res=min(res,t-s);
			}
			printf("%d\n",res);
		}
	}
	
	return 0;
}

思路二

#include <iostream>
#include <cstdio>
using namespace std;

const int N=100010;
int a[N];

int main()
{
    
    
	int x;
	scanf("%d",&x);
	int n,S;
	while(x--)
	{
    
    
		scanf("%d %d",&n,&S);
		for(int i=0;i<n;i++)
		{
    
    
			scanf("%d",&a[i]);
		}
		int res=n+1;  //这里取res=n+1而不是res=n,便于判断无解情况
		int s=0,t=0,sum=0;
		for(;;)
		{
    
    
			while(t<n&&sum<S)
			{
    
    
				sum+=a[t++];
			}
			if(sum<S) break;
			res=min(res,t-s);
			sum-=a[s++];
		}
		if(res>n)   //无解情况
		{
    
    
			res=0;
		}
		printf("%d\n",res);
	}
	
	return 0;
}

总结

尺取法(又称为:双指针、two pointers),是算法竞赛中一个常用的优化技巧,用来解决序列的区间问题,操作简单、容易编程。尺取法通常是指对数组保存一对下标(起点、终点),然后根据实际情况交替推进两个端点直到得出答案的方法。如果区间是单调的,也常常用二分法来求解,所以很多问题用尺取法和二分法都行。

最长连续不重复子序列

给定一个长度为n的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。

输入格式
第一行包含整数n。

第二行包含n个整数(均在0~100000范围内),表示整数序列。

输出格式
共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。

数据范围
1≤n≤100000

输入样例

5
1 2 2 3 5

输出样例

3

分析
定义两个指针i,j(j<i),用数组s[N]存放每个元素的出现次数,从前到后扫描,不断更新区间的最长长度

AC代码

#include <iostream>

using namespace std;

const int N=100010;
int a[N];
int s[N];
int n;

int main()
{
    
    
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>a[i];
    int res=0;
    for(int i=0,j=0;i<n;i++)
    {
    
    
        s[a[i]]++;
        while(s[a[i]]>1)
        {
    
    
            s[a[j]]--;
            j++;
        }
        res=max(res,i-j+1);
    }
    cout<<res<<endl;
        
    return 0;
}

数组截取

题目描述
有一段数组n 从中截取和为k的一部分 最长可以截取多长?(如果截取不到正好为k的情况下输出-1)
注意:
出题人:出个数组截取吧;验题人:数据太弱了加强一下;出题人:陷入沉思,OK

本题因为验题人吐槽,数据太弱所以加强了一点点,一点点…
因为数据比较多 请使用你认为最快的读取方式 最省内存的运算方法。反正C++标程 500ms 256mb 内跑过去了。

输入描述:

第一行输入两个整数 n,k
1<n<=2×107
0<=k<=1018
第二行输入n个正整数(包含0)
a1 a2 …an (0<=ai<=1012) 1<=i<=n

输出描述:

输出运算结果

示例1

输入

10 3
3 3 1 1 1 0 0 0 3 3

输出

6

示例2

输入

10 3
6 6 6 6 6 6 6 6 6 3

输出

1

备注:

注意数据及其多 请使用快速的读取方式
以及内存很小 不要浪费 哦

分析
尺取法,定义两个指针i,j,i在前,j在后。当sum>k的时候,j右移;当sum<k时,i右移,当sum==k时,更新最长长度
时间复杂度O(n)

AC代码

#include <bits/stdc++.h>

using namespace std;
typedef  long long ll;

ll n,k;
const int N=2e7+10;

ll a[N];
static char buf[100000], * pa = buf, * pb = buf;
#define gc pa==pb&&(pb=(pa=buf)+fread(buf,1,100000,stdin),pa==pb)?EOF:*pa++
inline long long int read()
{
    
    
    long long int x(0); char c(gc);
    while (c < '0' || c>'9')c = gc;
    while (c >= '0' && c <= '9')x = (x << 1) + (x << 3) + (c ^ 48), c = gc;
    return x;
}

int main()
{
    
    
	n=read(),k=read();
	for(int i=1;i<=n;i++)
	{
    
    
		a[i]=read();
	}
	ll res=-1;
	ll sum=0;
	ll j=1;
	for(int i=1;i<=n;i++)
	{
    
    
		sum += a[i];
        while (sum > k) {
    
    
          sum -= a[j];
          j++;
        }
        if (sum == k) {
    
    
          res = max(res, i - j + 1);
        }
	}
	cout<<res<<endl;
	
	return 0;
}

注意
本题输入数据规模较大,要使用快读

Jessica’s Reading Problem

Description

Jessica’s a very lovely girl wooed by lots of boys. Recently she has a problem. The final exam is coming, yet she has spent little time on it. If she wants to pass it, she has to master all ideas included in a very thick text book. The author of that text book, like other authors, is extremely fussy about the ideas, thus some ideas are covered more than once. Jessica think if she managed to read each idea at least once, she can pass the exam. She decides to read only one contiguous part of the book which contains all ideas covered by the entire book. And of course, the sub-book should be as thin as possible.

A very hard-working boy had manually indexed for her each page of Jessica’s text-book with what idea each page is about and thus made a big progress for his courtship. Here you come in to save your skin: given the index, help Jessica decide which contiguous part she should read. For convenience, each idea has been coded with an ID, which is a non-negative integer.

Input

The first line of input is an integer P (1 ≤ P ≤ 1000000), which is the number of pages of Jessica’s text-book. The second line contains P non-negative integers describing what idea each page is about. The first integer is what the first page is about, the second integer is what the second page is about, and so on. You may assume all integers that appear can fit well in the signed 32-bit integer type.

Output

Output one line: the number of pages of the shortest contiguous part of the book which contains all ideals covered in the book.

Sample Input

5
1 8 8 8 1

Sample Output

2

题目大意
Jessica需要阅读连续的几页书,并且这几页书要将全部知识点覆盖,求需要阅读的最少页数

AC代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <set>

using namespace std;
const int MAX_P=1000005;
int P;
int a[MAX_P];

void solve()
{
    
    
    set<int> all;
    for(int i=0;i<P;i++)
    {
    
    
        scanf("%d",&a[i]);
        all.insert(a[i]);
    }
    int n=all.size();
    int s=0,t=0,num=0;
    map<int,int> cnt;  //建立知识点到出现次数的映射
    int res=P;
    for(;;)
    {
    
    
        while(t<P&&num<n)
        {
    
    
            if(cnt[a[t++]]++==0)  //出现新的知识点
                num++;
        }
        if(num<n)
            break;
        res=min(res,t-s);
        if(--cnt[a[s++]]==0)  //某个知识点的出现次数为0
        {
    
    
            num--;
        }
    }

    printf("%d\n",res);
}

int main()
{
    
    
    scanf("%d",&P);
    solve();

    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_46155777/article/details/109256193
今日推荐