一、双指针算法两种常见的问题分类:
(1)对于两个序列,维护某种次序,比如归并中合并两个有序序列的操作
(2)对于一个序列,用两个指针维护一段区间
如图:
注意,这里虽然说双指针,但不代表是真正的两个指针,你可以理解为两个变量i,j
但是这两个变量i,j它们是一直向前行进的,是不会后退,就拿KMP来说,其实它也用到双指针的思想以及归并排序当中的合并两个区间的代码。
尺取法,这个说到底就是双指针思想衍生出来的,那什么是尺取法呢?你就理解为一把可长可短的尺子,推进你要遍历的元素
尺取法:顾名思义,像尺子一样取一段,通常是对数组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左右端点以得出答案。之所以需要掌握这个技巧,是因为尺取法比直接暴力枚举区间效率高很多,尤其是数据量大的时候,所以尺取法是一种高效的枚举区间的方法,一般用于求取有一定限制的区间个数或最短的区间等等。
在对所选取区间进行判断之后,我们可以明确如何进一步有方向地推进区间端点以求解满足条件的区间。
明确题目所需要求解的量之后,区间左右端点一般从最整个数组的起点开始,之后判断区间是否符合条件在根据实际情况变化区间的端点求解答案。
二、核心模板 :
朴素算法:
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
//暴力方式:O(n^2)
双指针算法:
先写一个朴素的算法,寻找 i,j 有没有单调的关系,利用这些单调的关系,将上面的朴素算法算法优化到O(n)
for(i=0,j=0;i<n;i++) //这个主要是遍历的范围
{
while(j<i&&check(i,j)) j++; //i,j两个指针所表示的范围,在这个范围内的值都符合题目要求
//每道题的具体逻辑
//优化到O(n)
}
自己常用的写法:
while(1){
while(head<tail && chenk() ) 进入窗口的代码;
if(。。。。) break ; //这里就是判断不满足情况时应该退出循环的条件,
//也就是head>=tail 且check函数为fasle的时候进行每道题的具体处理逻辑代码
++head; //主要是为了推进窗口向前滑动
}
例题展示:
例题一:
给定一个长度为n的整数序列,请找出最长的不包含重复数字的连续子串
#include<iostream>
using namespace std;
const int N=100010;
int n;
int a[N],s[N];
int main()
{
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(s[a[i]]>1)
{
s[a[j]]--;
j++;
}
res=max(res,i-j+1);
}
cout<<res<<endl;
return 0;
}
例题二(经典题):
给定长度n的整数列a0,…,an-1以及S。求总和不小于S的连续子序列长度最小值。
#include <iostream>
#include <cmath>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
ll a[maxn];
ll n;
ll s;
int main() {
//ios::sync_with_stdio(0);
//cin.tie(0);
int T;
cin>>T;
while (T--) {
cin>>n>>s;
for (int i=0; i<n; i++) cin>>a[i];
ll l=0,r=0,sum=0;
ll ans=n+1;
while (1) {
while (r<n && sum<s)
sum+=a[r++];
if (sum<s) break;
ans=min(ans,r-l);
sum-=a[l++];
}
if (ans>n) ans=0;
cout<<ans<<endl;
}
return 0;
}
例题三:
一本书P页,第i页有知识点ai,同一个知识点可能多次提到,希望通过连续的一些页把所有知识点都覆盖到。求出连续的最少页数。
#include <iostream>
#include <algorithm>
#include <cmath>
#include <set>
#include <map>
#include <cstdio>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int maxn=1e6+10;
int a[maxn];
int n;
set <int> s;
map <int,int> vis;
int main() {
//ios::sync_with_stdio(0);
//cin.tie(0);
scanf("%d",&n);
for (int i=0; i<n; i++) {
scanf("%d",&a[i]);
s.insert(a[i]);
}
int len=s.size();
int l=0,r=0,cnt=0;
int ans=n;
while (1){
while (r<n && cnt<len){
if (vis[a[r]]==0)
cnt++;
vis[a[r]]++;
r++;
}
if (cnt<len) break;
ans=min(ans,r-l);
vis[a[l]]--;
if (vis[a[l++]]==0)
cnt--;
}
printf("%d\n",ans);
return 0;
}