双指针
双指针算法应用范围十分广:快速排序(单个序列)、归并排序(两个序列)…
// 朴素算法 o(n²)
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
// 具体算法
}
}
// 双指针算法 o(n)
for(int i=0,j=0;i<n;i++){
while(i<n&&check(i,j)) i++;
// 具体算法
}
将朴素算法的O(n2)的时间复杂度优化到O(n)
例题
1.最长连续不重复子序列
给定一个长度为n的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
输入格式
第一行包含整数n。
第二行包含n个整数(均在0~100000范围内),表示整数序列。
输出格式
共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。
数据范围
1≤n≤100000
输入样例:
5
1 2 2 3 5
输出样例:
3
题目分析
我们用 i 和 j 模拟两个指针,i 在左边,j 在右边,i 不断向右移动,并将 a[ i ] 放到 s[ a[ i ] ] 中,当 s[ a[ i ] ] > 1 时,说明遇到了重复的元素,此时,将 j 右移,同时将 s[ a [ j ] ] – ,直到 s [ a [ i ] ] 为1,说明 i 和 j 相遇了
代码样例
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<sstream>
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 100000;
const int MOD = 1000000007;
const int INF = 0x3f3f3f3f;
int gcd(int a, int b){return b ? gcd(b, a % b) : a;}
int n;
int a[N], s[N];
int ans;
int main()
{
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(j < i && s[a[i]] > 1)
{
s[a[j]] -- ;
j ++ ;
}
ans = max(ans, i - j + 1);
}
cout << ans << endl;
return 0;
}
2.数组元素的目标和
给定两个升序排序的有序数组A和B,以及一个目标值x。数组下标从0开始。
请你求出满足A[i] + B[j] = x的数对(i, j)。
数据保证有唯一解。
输入格式
第一行包含三个整数n,m,x,分别表示A的长度,B的长度以及目标值x。
第二行包含n个整数,表示数组A。
第三行包含m个整数,表示数组B。
输出格式
共一行,包含两个整数 i 和 j。
数据范围
数组长度不超过100000。
同一数组内元素各不相同。
1≤数组元素≤109
输入样例:
4 5 6
1 2 4 7
3 4 6 8 9
输出样例:
1 1
题目分析
这里两个数组的长度均为105 如果至今暴力求解的话,时间一定会超时,但是由于两个数组是有序的,所以第一个数组从前往后遍历,第二个数组从后往前遍历,如果 i 一定对于后面的 j 来说,如果和大于 x 的话,那么后续的 i 对应的和也一定会大于 x ,所以一个从前往后,一个从后往前,这样遍历的话时间复杂度会控制在105 左右
代码样例
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<sstream>
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 100010;
const int MOD = 1000000007;
const int INF = 0x3f3f3f3f;
int gcd(int a, int b){return b ? gcd(b, a % b) : a;}
int n, m, x;
int a[N], b[N];
int ans;
int main()
{
cin >> n >> m >> x;
for(int i = 0; i < n; i ++ ) cin >> a[i];
for(int i = 0; i < m; i ++ ) cin >> b[i];
for(int i = 0, j = m - 1; i < n; i ++ )
{
while(a[i] + b[j] > x && j >= 0) j -- ;
if(a[i] + b[j] == x && j >= 0) cout << i << ' ' << j << endl;
}
return 0;
}
3.判断子序列
给定一个长度为 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<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<sstream>
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 100000;
const int MOD = 1000000007;
const int INF = 0x3f3f3f3f;
int gcd(int a, int b){return b ? gcd(b, a % b) : a;}
int n, m;
int a[N], b[N];
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i ++ ) cin >> a[i];
for(int i = 0; i < m; i ++ ) cin >> b[i];
int i = 0, j = 0;
while(i < n)
{
if(a[i] == b[j])
{
i ++ ;
j ++ ;
}
else j ++ ;
if(j == m && i < n)
{
cout << "No" << endl;
break;
}
}
if(i == n && j <= m) cout << "Yes" << endl;
return 0;
}
总结
双指针算法总体上来说是一种简化思想,可以简化时间复杂度,避免时间超限的情况,通常来说,使用双指针算法时,可以从题目中找到一定的规律,单调性等…