双指针算法——AcWing 2816. 判断子序列、799.最长连续不重复子序列、800. 数组元素的目标和

双指针算法

AcWing 2816. 判断子序列

给定一个长度为 n 的整数序列 a1,a2,…,an 以及一个长度为 m 的整数序列 b1,b2,…,bm

请你判断 a 序列是否为 b 序列的子序列。

子序列指序列的一部分项按原有次序排列而得的序列,例如序列 {a1,a3,a5} 是序列 {a1,a2,a3,a4,a5} 的一个子序列。

输入格式
第一行包含两个整数 n,m。

第二行包含 n 个整数,表示 a1,a2,…,an

第三行包含 m 个整数,表示 b1,b2,…,bm

输出格式
如果 a 序列是 b 序列的子序列,输出一行 Yes。 否则,输出 No。

数据范围
1≤n≤m≤105,
−109≤ai,bi≤109
输入样例:

3 5
1 3 5
1 2 3 4 5

输出样例:

Yes

思路:

子串不要求连续,有对应的数字即可,因此可以在遍历一遍父串的同时,判断该值是否与子串的某一值相等,如果相等,则匹配子串的下一个值。由此可以看出,子串的遍历是单调向右一直进行的。

#include <bits/stdc++.h>

using namespace std;

const int N=1e5+10;
int a[N],b[N];

int main() {
    
    
    int n,m;
    cin>>n>>m;
    for (int i = 0; i < n; ++i)   scanf("%d",a+i);//子串
    for (int i = 0; i < m; ++i)   scanf("%d",b+i);//父串
    for (int i = 0,j = 0; i < m; ++i) {
    
    
        if (b[i]==a[j]) j++;     //本来这里写的while (b[i]==a[j]) j++; 错误
        if (j==n)  {
    
    cout<<"Yes";  return 0;} //而且这里应该不用写j<=i的范围限制,因为
    }                          //i++的次数肯定要多于j++的次数,即i一定大于等于j
    cout<<"No";

    return 0;
}

分析:

对于第14行if (b[i] == a[j]) j++;,不能写成while (b[i]==a[j])
j++;
,因为根据我们上面分析的思路,当遇到相等时,j只需移动到子串的下一个位置即可,不需要做背的操作。

AcWing 799. 最长连续不重复子序列

给定一个长度为n的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
输入格式:
第一行包含整数n。
第二行包含n个整数(均在0~100000范围内),表示整数序列。
输出格式:
共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。

数据范围:

1≤n≤100000

输入样例:

5
1 2 2 3 5

输出样例:

3

思路:

从0到n-1遍历数组a,对于每一个a[i]寻找此时满足条件的最长的区间长度,右边界定义为 i ,依次遍历;左边界定义为 j ,为求出最大长度,力求每次 j 一定在 i 的最左边处,下一个a[i+1]满足条件的最长区间长度是在上一个的基础上进行的:每次都已知上一次的满足条件的最大区间范围,当右边界往后移动一位,左边界一定是只能往后移动,然后判断区间内是否有重复的数字决定 j 是否向后移动,以此确定当前a[i+1]下的满足条件的最大区间长度。

怎么判断区间内是否有重复的数字?
这里巧妙的利用了桶排序的思想和计网中滑动窗口的概念,已知上一次的最大区间范围,所以当右边界后移一位时,一旦有重复,那肯定是这个数字出现了重复。
i 后移,加入统计;j 后移,移除统计。

#include <bits/stdc++.h>

using namespace std;

const int N=1e5+10;
int a[N],S[N];

int main() {
    
    
    int n,ans=0;
    cin>>n;
    for (int i = 0; i < n; ++i)  cin>>a[i];
    for (int i = 0,j = 0; i < n; ++i) {
    
     //全部遍历一遍,取最大值
        S[a[i]]++;
        while (S[a[i]]>1)  {
    
    S[a[j]]--;  j++;} //这里是while循环,当出现重复时,必须要使j一直后移,确保一直移动到第一次出现a[i]的下一个位置
        ans=max(ans,i-j+1);
    }
    cout<<ans;
    return 0;
}

AcWing 800. 数组元素的目标和

给定两个升序排序的有序数组A和B,以及一个目标值x。数组下标从0开始。
请你求出满足A[i] + B[j] = x的数对(i, j)。

数据保证有唯一解。

输入格式

第一行包含三个整数n,m,x,分别表示A的长度,B的长度以及目标值x。

第二行包含n个整数,表示数组A。

第三行包含m个整数,表示数组B。

输出格式

共一行,包含两个整数 i 和 j。

数据范围

数组长度不超过100000。 //1e5
同一数组内元素各不相同。 1≤数组元素≤109

输入样例:

4 5 6
1 2 4 7
3 4 6 8 9

输出样例:

1 1    //A[1] + B[1] = 2+4=6 = x

思路:

两个数组按升序排列:i 从0开始,从前往后遍历数组a,j从m-1开始,从后往前遍历数组b。
①定 j 变 i :直到找到第一个满足条件a[i] + b[j] > x的 i
②定 i 变 j:如果a[i] + b[j] > x一直成立,就一直j- -,直到小于等于x。
如果是等于x退出while循环,由于题目有唯一解,就已经找到答案了,可以输出并退出了。
如果是小于x,说明此时的 i 不满足条件,继续重复上面的①②步骤。

注意 i 越往右越大,当 a[i]+b[j]>x时,a[i+1]+b[j]>x必成立; a[i]+b[j]<x时,a[i+1]+b[j]与x大小不定,对于每次 i 右移,j只能保持不动或者左移,必不能右移,因此可以采用双指针算法。i 递增 ,j 递减。

#include <bits/stdc++.h>

using namespace std;

const int N=1e5+10;
int A[N],B[N];

int main() {
    
    
    int n, m, x;
    cin >> n >> m >> x;
    for (int i = 0; i < n; ++i) cin >> A[i];
    for (int i = 0; i < m; ++i) cin >> B[i];//数值升序排列
    int i=0,j=m-1;
    for ( ; i < n; ++i) {
    
    
        while (j>=0&&A[i]+B[j]>x) j--;  //固定一方最大,另一方从小遍历,找到第一个大于x的值,再减小最大值的一方
        if (A[i]+B[j]==x) break;//当i增大时,j只能往左减小,往右必不成立
    }
    cout<<i<<" "<<j;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/HangHug_L/article/details/112990748