双指针
-
核心思想(作用):优化
-
在利用双指针算法解题时,考虑原问题如何用暴力算法解出,观察是否可构成单调性,若可以,就可采用双指针算法对暴力算法进行优化.
-
基本思想:在重复遍历对象的过程中,不是在两个循环中使用单个指针进行重复访问,而是在一个循环中使用两个相同方向(快慢指针)或者相反方向(对撞指针)的指针进行访问。
-
双指针将一个两层循环转化成了一层循环,时间复杂度也从n^2变成了n,那么什么时候会需要使用双指针呢?
一般来讲,需要对一个数组进行重复遍历时,可以使用双指针法
-
区别:
(1) 朴素算法每次在第二层遍历的时候,是会从新开始(j会回溯到初始位置),然后再遍历下去。(假设i是终点,j是起点)(2) 双指针算法:由于具有某种单调性,每次在第二层遍历的时候,不需要回溯到初始位置(单调性),而是在满足要求的位置继续走下去或者更新掉。
-
常见问题分类:
(1) 对于一个序列,用两个指针维护一段区间
(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
-
模板
for (int i = 0, j = 0; i < n; i ++ )
{
while (j < i && check(i, j)) j ++ ;
// 具体问题的逻辑
}
例题 1
输入一个字符每个子串之间有一个空格,输出每一个空格后的子串。
输入
abc def hij
输出
abc
def
hij
#include<bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
char str[1000];
gets(str);
int n = strlen(str);
for(int i = 0; i < n; i++){
//双指针
int j = i;
while(j < n && str[j]!=' ') j++;
//这道题的具体逻辑
for(int k = i; k < j; k++) cout << str[k];
cout << '\n';
}
return 0;
}
例题 2
给定一个长度为n的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
输入格式
第一行包含整数 n。
第二行包含 n 个整数(均在 0∼105范围内),表示整数序列。
输出格式
共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。
数据范围
1≤n≤10^5
输入样例:
5
1 2 2 3 5
输出样例:
3
分析
首先遍历数组 a 中的每一个元素 a[i] ,对于每一个 i ,找到 j 使得双指针 [j,i] 维护的是以 a[i] 结尾的最长连续不重复子序列,长度为 i - j + 1,将这一长度与 r 的较大者更新给 r。
由于 [j, i - 1] 是前一步得到的最长连续不重复子序列,所以如果[j,i]中有重复元素,一定是 a[i],因此右移 j 直到 a[i] 不重复为止
用数组 s 记录子序列 a[j ~i] 中各元素出现次数,遍历过程中对于每一个i有四步操作:cin元素a[i] -> 将 a[i] 出现次数 s[a[i]] 加1 -> 若a[i]重复则右移 j(s[a[j]]要减1) -> 确定 j 及更新当前长度 i - j + 1 给 r
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int a[N], s[N];
int n;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
int res = 0;
for(int i = 0, j = 0; i < n; i++){
cin >> a[i];
s[a[i]]++;
while(s[a[i]]>1){
s[a[j]]--;
j++;
}
res = max(res, i-j+1);
}
cout << res;
return 0;
}
例题 2 扩展
如果找的是最长不连续并且不重复的子序列的话,直接整个原数组去重就可,剩下的就是最长不连续并且不重复的子序列