基础算法 ---- 双指针算法

本文深入探讨双指针算法的应用技巧,通过实例讲解如何利用该算法解决最长连续不重复子序列、数组元素的目标和等问题,有效降低时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

双指针

双指针算法应用范围十分广:快速排序(单个序列)、归并排序(两个序列)…

// 朴素算法   o(n²)
for(int i=0;i<n;i++){
	for(int j=0;j<n;j++){
	// 具体算法
	}
}
// 双指针算法   o(n)
for(int i=0,j=0;i<n;i++){
	while(i<n&&check(i,j)) i++;
	// 具体算法
}

将朴素算法的O(n2)的时间复杂度优化到O(n)

例题

1.最长连续不重复子序列

给定一个长度为n的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。

输入格式

第一行包含整数n。

第二行包含n个整数(均在0~100000范围内),表示整数序列。

输出格式

共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。

数据范围
1≤n≤100000
输入样例:
5
1 2 2 3 5
输出样例:
3
题目分析

我们用 i 和 j 模拟两个指针,i 在左边,j 在右边,i 不断向右移动,并将 a[ i ] 放到 s[ a[ i ] ] 中,当 s[ a[ i ] ] > 1 时,说明遇到了重复的元素,此时,将 j 右移,同时将 s[ a [ j ] ] – ,直到 s [ a [ i ] ] 为1,说明 i 和 j 相遇了

代码样例
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<sstream>

#define x first
#define y second

using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 100000;
const int MOD = 1000000007;
const int INF = 0x3f3f3f3f;

int gcd(int a, int b){return b ? gcd(b, a % b) : a;}

int n;
int a[N], s[N];
int ans;
int main()
{

	cin >> n;
	for(int i = 0; i < n; i ++ ) cin >> 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 ++ ;
		}
		ans = max(ans, i - j + 1);
	}
	cout << ans << endl;
	return 0;
}




2.数组元素的目标和

给定两个升序排序的有序数组A和B,以及一个目标值x。数组下标从0开始。
请你求出满足A[i] + B[j] = x的数对(i, j)。

数据保证有唯一解。

输入格式

第一行包含三个整数n,m,x,分别表示A的长度,B的长度以及目标值x。

第二行包含n个整数,表示数组A。

第三行包含m个整数,表示数组B。

输出格式

共一行,包含两个整数 i 和 j。

数据范围
 数组长度不超过100000。 
同一数组内元素各不相同。
 1≤数组元素≤109
输入样例:
4 5 6
1 2 4 7
3 4 6 8 9
输出样例:
1 1
题目分析

这里两个数组的长度均为105 如果至今暴力求解的话,时间一定会超时,但是由于两个数组是有序的,所以第一个数组从前往后遍历,第二个数组从后往前遍历,如果 i 一定对于后面的 j 来说,如果和大于 x 的话,那么后续的 i 对应的和也一定会大于 x ,所以一个从前往后,一个从后往前,这样遍历的话时间复杂度会控制在105 左右

代码样例
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<sstream>

#define x first
#define y second

using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 100010;
const int MOD = 1000000007;
const int INF = 0x3f3f3f3f;

int gcd(int a, int b){return b ? gcd(b, a % b) : a;}

int n, m, x;
int a[N], b[N];
int ans;

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 ++ )
	{
		while(a[i] + b[j] > x && j >= 0) j -- ;
		if(a[i] + b[j] == x && j >= 0) cout << i << ' ' << j << endl;
	}
	return 0;
}




3.判断子序列

给定一个长度为 n 的整数序列 a1,a2,…,an 以及一个长度为 m 的整数序列 b1,b2,…,bm。

请你判断 a 序列是否为 b 序列的子序列。

子序列指序列的一部分项按原有次序排列而得的序列,例如序列 {a1,a3,a5} 是序列 {a1,a2,a3,a4,a5} 的一个子序列。

输入格式

第一行包含两个整数 n,m。

第二行包含 n 个整数,表示 a1,a2,…,an。

第三行包含 m 个整数,表示 b1,b2,…,bm。

输出格式

如果 a 序列是 b 序列的子序列,输出一行 Yes。

否则,输出 No。

数据范围
1≤n≤m≤105,
−109≤ai,bi≤109
输入样例:
3 5
1 3 5
1 2 3 4 5
输出样例:
Yes
题目分析

这道题目基本上就是暴力遍历了,和双指针相关不大

代码样例
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<sstream>

#define x first
#define y second

using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 100000;
const int MOD = 1000000007;
const int INF = 0x3f3f3f3f;

int gcd(int a, int b){return b ? gcd(b, a % b) : a;}

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 = 0, j = 0;
	while(i < n)
	{
		if(a[i] == b[j])
		{
			i ++ ;
			j ++ ;
		}
		else j ++ ;
		if(j == m && i < n)
		{
			cout << "No" << endl;
			break;
		}
	}
	if(i == n && j <= m) cout << "Yes" << endl;
	return 0;
}




总结

双指针算法总体上来说是一种简化思想,可以简化时间复杂度,避免时间超限的情况,通常来说,使用双指针算法时,可以从题目中找到一定的规律,单调性等…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在人间负债^

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值