双指针算法以及一些典型例题

一、双指针算法两种常见的问题分类:

(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;
}

一篇例题博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值