双指针算法
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;
}