0
点赞
收藏
分享

微信扫一扫

双指针,尺取法小结


双指针,尺取法小结

  • ​​双指针介绍​​
  • ​​题型总结​​
  • ​​例题分析​​
  • ​​做题总结​​

双指针介绍

一般用于做具有单调性的,满足某一性质的区间问题。常见的快慢指针,对撞指针,滑动窗口)貌似有些人称之为尺取法

进入屡次被相关题目按在地上摩擦,便简单地进行整理小结,本文仅介绍一些极其基础的双指针题目,可能还有错误,大佬轻喷

题型总结

  1. 求满足某一性质的最短/最长连续子序列
  2. 字符串匹配
  3. 维护升序
  4. 寻找满足某一条件的点对/数值

例题分析

​​T1 数组截取​​

双指针,尺取法小结_#include


以左端点为基准写法(这个想起来比较顺,但是貌似适应性不强?)

分析:

前缀和预处理

枚举遍历枚举左端点,扩展右端点

区间和三种状态

<k继续扩展直到不大于k

=k继续扩展直到不大于k(有可能后面为0呢)

> k停止扩展

答案维护:如果=k则对答案进行更新维护

#include<bits/stdc++.h>
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
typedef long long ll;
inline char nc(){
static char buf[100000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline ll _read()
{
char ch=nc();
ll sum=0;
while(!(ch>='0'&&ch<='9'))ch=nc();
while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=nc();
return sum;
}
ll n,k;
ll R=1,Ans=-1;
const int N=2e7+10;
ll sum[N];
int main()
{
n =_read() , k = _read();
for(int i = 1 ; i <= n ; i ++) sum[i] = sum[i - 1] + _read();
for(int i = 1 ; i <= n ; i ++)
{
while(R < n && sum[R + 1] - sum[i - 1] <= k) R ++;
if(sum[R] - sum[i - 1] == k)Ans = max(Ans,R - i + 1);
}
cout << Ans;
return 0;
}

以右端点为基准写法(这个写法的话普适性比较强)
遍历右端点
边界:左端点<=右端点,和>k
操作:在边界内,sum>k就一直缩小,当sum=k时更新答案

#include<bits/stdc++.h>
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
typedef long long ll;
inline char nc(){
static char buf[100000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline ll _read()
{
char ch=nc();
ll sum=0;
while(!(ch>='0'&&ch<='9'))ch=nc();
while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=nc();
return sum;
}
ll n,k;
int R=1,Ans=-1;
const int N=2e7+10;
ll sum[N];
int main()
{

n =_read() , k = _read();
for(int i = 1 ; i <= n ; i ++) sum[i] = sum[i - 1] + _read();
for(int i = 1,j=1; i <= n ; i ++)
{
while(j<=i&& sum[i] - sum[j - 1]>k) j++;
if(sum[i]-sum[j-1]==k)Ans=max(Ans,i-j+1);
}
cout << Ans;
return 0;
}

​​T2 Blash数集​​

分析:
快慢指针,作用维护升序
哪个小移动哪个,相同两个都移动(集合的概念无重复)
边界:cnt达到n

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,a,cnt;
const int N=1e5+10;
int q[N];
int main()
{
int posa=1,posb=1,cnt=1;
scanf("%lld%lld",&a,&n);
q[1]=a;
while(cnt<=n)
{
int x=q[posa]*2+1;
int y=q[posb]*3+1;
if(x<y)
{
q[++cnt]=x;
posa++;
}
else if(x>y)
{
q[++cnt]=y;
posb++;
}
else
{
q[++cnt]=x;
posa++;
posb++;
}
}
printf("%lld\n",q[n]);
return 0;
}

​​T3 相似的数集高级版​​

分析:
​​学长的博客​​ 快慢指针找两个集合交集元素个数(相等元素个数)
边界:其中一个指针到达最后一个元素就结束所以复杂度约为O(N)
原理利用单调性,两个集合元素单调递增

即存在这样关系
①A[posA]<A[posA+1]
②B[posB]<B[posB+1]
对于两个位置的元素他们右如下关系
A[posA]<B[posB]:
A[posA]<B[posB]<B[posB+1]
操作:为了令A=B,A向右移动
A[posA]=B[posB]:
A[posA]=B[posB]<A[posA+1]
A[posA]=B[posB]<B[posB+1]
操作:维护更新答案,A,B一起向右移动(其实随便移动一个后另外一个必定还要移动)
A[posA]>B[posB]:
B[posB]<A[posA]<A[posA+1]
操作:为了令A=B,B向右移动

​​T4 判断子序列​​

分析:
边界:两个指针不越界pa<=n&&<pb<=m
以子序列为基准匹配原序列
如果相同则子序列和原序列指针同时右移动进行下一位匹配
如果不同,移动原序列

#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=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=m;i++)cin>>b[i];
int pa=1;
int pb=1;
while(pa<=n&&pb<=m)
{
if(a[pa]!=b[pb])pb++;
else pa++,pb++;
}
if(pa==n+1)puts("Yes");
else puts("No");
return 0;
}

​​T5 最长连续不重复子序列​​

假的双指针(1800+ms)
分析:
以左端点为基准(可能是我不会写左边的缘故)
遍历左端点,扩展右端点直到碰到重复
更新答案(注意此时右端点是已经重复的那个),询问下一左端点

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
bool tj[N];
int a[N];
int n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
int l=1,r=1,ans=1;
while(l<=n)
{
memset(tj,0,sizeof tj);
while(!tj[a[r]]&&r<=n)
{
tj[a[r]]=1;
r++;
}
ans=max(ans,r-l);
l++;r=l;
}
cout<<ans;
return 0;
}

真的双指针(50+ms)
分析:
以右端点为基准遍历
边界:左端点<=右端点,包含重复
只要区间包含重复,左端点缩减,并消除标记
当不包含重复的时候更新答案

#include <iostream>

using namespace std;

const int N = 100010;
int a[N], s[N];

int main()
{
int n = 0, res = 0;
cin >> n;

for(int i = 0; i < n; i ++ ) scanf("%d", &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 ++ ;
}
res = max(res, i - j + 1);
}

cout << res << endl;
return 0;
}

​​T6 数组元素的目标和​​​ 分别从左端和右端开始找
如果a[i]+b[j]>x j向左移动尽可能让他变小
接下来两种可能(等于x和小于x)
如果a[i]+b[j]==x 输出答案
如果直接进入下一循环,i向右移动

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
typedef long long ll;
ll a[N],b[N];
ll n,m,x;
int main()
{
scanf("%lld%lld%lld",&n,&m,&x);
for(int i=0;i<n;i++)scanf("%lld",&a[i]);
for(int i=0;i<m;i++)scanf("%lld",&b[i]);
for(int i=0,j=m-1;i<n;i++)
{
while(j>=0&&a[i]+b[j]>x)j--;
if(a[i]+b[j]==x)
{
printf("%d %d",i,j);
break;
}
}
return 0;
}

做题总结

比较通用的一个代码结构

for(int i = 0, j = 0; i < n; i ++ )//考虑起点,都为开头/两端什么的
{
//check函数一般反着写,while循环一直缩小边界,直到可能为答案的区间
//注意是可能,比如上面一些题目<和=就是可能的情况,但是更新答案只在=的时候
while (j < i && check(i, j)) j ++ ;//注意边界问题
//更新答案
}

需要区分的一个情况
求满足某一性质的最短/最长连续子序列,注意这里的连续。
区分几个概念
子序列:从原序列中抽掉几个元素,剩下的序列为子序列,子序列不一定连续
子序列要特别说明了才算连续
子串:字符串中任取l,r他们截出来的连续字符串就是子串
比较典型的就是:
​​​最长上升子序列​​ 我在写这篇博客的时候瞄了眼这题,起手打了个双指针后来发现貌似不对,这题用DP做比较好

另外:文末推荐可以看看洛谷的这篇日报:​​尺取法小结​​


举报

相关推荐

0 条评论