双指针算法时间复杂度O(n)
一般双指针算法运用于有序的某一个或两个序列中,从O(n2)优化到O(n)
算法模板
for (int i = 0, j = 0; i < n; i ++ )
{
while (j < i && check(i, j)) j ++ ;
// 具体问题的逻辑
}
常见问题分类:
(1) 对于一个序列,用两个指针维护一段区间
(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
补充关于字串与子序列的区别:

第一类双指针:指向两个序列
例:归并排序,acwing2816. 判断子序列,acwing800. 数组元素的目标和
acwing800. 数组元素的目标和
算法一: 双指针O(n + m)
假定两个指针
i
指向a
数组的初始位置,j
指向b
数组的终点
由于两个序列都是有序的, 那么如果a[i] + b[j] > x
仅仅回退j
指针即可(a[i] + b[j] > x那么i往后移动必然会导致a[i] + b[j]的值会更大只有减小b[j], 才有可能使得a[i] + b[j] <= x
), 当a[i] + b[j] > x
,那么j
就不能再回退了,若是再回退必然会导致a[i] + b[j]更小, 这时只需要不动j, 去将i指针向后移动即可
,如此迭代找到a[i] + b[j] == x,双指针优化时j指针不会每次都再回退到b数组的末尾,从而优化了时间复杂度
code:
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n, m, x;
int a[N], b[N];
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 ++ )
{
//j > 0保证数组不会越界,这里数据保证了一定会有解,加不加都行,感觉还是习惯性还是加上好点
while(j > 0 && a[i] + b[j] > x) j --;
//退出循环a[i] + b[j] <= x,那么直接判断是否等于x,等于输出,不等于就直接将i往后移动
if(a[i] + b[j] == x) printf("%d %d\n", i, j);
}
return 0;
}
算法二:二分O(nlogm)
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int n, m, x;
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; i < n; i ++ )
{
//二分模板,当然这里也能用lower_bound()
int l = 0, r = m - 1;
while(l < r)
{
int mid = l + r >> 1;
if(b[mid] >= x - a[i]) r = mid;
else l = mid + 1;
}
if(a[i] + b[l] == x) cout << i << ' ' << l;
}
return 0;
}
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
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, j; //i指向b数组,j指向a数组
/*
注意这里的 j < n 就必须要写了, 若是不写那么当a[]循环完时,
那么a[]有效值之后的全是0,此时若是b[]中也是0,j就会错误匹配导致j的值>n
测试数据: a 1
b 1 0
*/
for(i = 0, j = 0; i < m; i ++)
if(j < n && a[j] == b[i]) j ++;
if(j == n) puts("Yes");
else puts("No");
return 0;
}
第二类双指针:指向一个序列
例:快速排序, acwing799. 最长连续不重复子序列
acwing799. 最长连续不重复子序列
对于一个序列总要有个快慢指针,外层循环放快指针,内层循环放慢指针
对于本题,用慢指针j,与快指针i进行维护一个区间,不断更新[j, i] 区间的大小,找到最大值。
由于要求是不重复的,所以当发生重复情况,那么一定是快指针i所指向的值与上一个值重复(这里开个数组记录一下每个元素出现的次数,若次数>1说明重复了,若数据较大,可用哈希代替数组)
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], s[N];
int main()
{
int n;
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(j < i && s[a[i]] > 1) s[a[j ++ ]] --;
//若执行了循环,当循环结束后,j刚好指向最后一个i指向的元素,此时i, j
res = max(res, i - j + 1);
}
cout << res << endl;
return 0;
}