常用双指针技巧可以分为两类,一类是「快慢指针」,一类是「左右指针」。笔者在本章只讲左右指针,主要解决数组(或者字符串)中的问题,比如二分查找,处理子序列。
左右指针
将给定的英文字符串进行反转。我们可以使用一头一尾的双指针。
例如: I love cpp 得到的结果是:ppc evol I
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int main()
{
string str;
getline(cin, str);
int i = 0, j = str.size() - 1;
while(i <= j )
{
swap(str[i], str[j]);
i ++, j-- ;
}
cout << str;
return 0;
}
其实只要一个for
循环倒着输出就行了
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int main()
{
string str;
getline(cin, str);
for(int j = str.size() - 1; j >= 0; j --)
cout<<str[j];
return 0;
}
好了,现在进入正题
数组元素的目标和 传送门
给定两个升序排序的有序数组 A 和 B,以及一个目标值 x。
数组下标从 0 开始。
请你求出满足 A[i]+B[j]=x 的数对 (i,j)。
数据保证有唯一解。
输入格式
第一行包含三个整数 n,m,x,分别表示 A 的长度,B 的长度以及目标值 x。
第二行包含 n 个整数,表示数组 A。
第三行包含 m 个整数,表示数组 B。
输出格式
共一行,包含两个整数 i 和 j。
数据范围
数组长度不超过 105。
同一数组内元素各不相同。
1≤数组元素≤109
输入样例:
4 5 6
1 2 4 7
3 4 6 8 9
输出样例:
1 1
i
从 0开始 从前往后遍历
j
从 m - 1
开始 从后向前遍历
由于数据保证有唯一解,双指针是可行的。
时间复杂度最坏为 O(n + m)
,比双重fo
r循环O(nm)
快
#include<iostream>
using namespace std;
const int N=1e5+10;
int a[N],b[N];
int main()
{
int n, m, x;
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(j >= 0 && a[i] + b[j] > x) j --;
if(a[i] + b[j] == x)
cout << i << ' ' << j;
}
return 0;
}
最长连续不重复子序列 传送门
给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
输入格式
第一行包含整数 n。
第二行包含 n 个整数(均在 0∼105 范围内),表示整数序列。
输出格式
共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。
数据范围
1≤n≤105
输入样例:
5
1 2 2 3 5
输出样例:
3
核心思路:
遍历数组a中的每一个元素a[i]
, 对于每一个i
,找到j使得双指针[j, i]
维护的是以a[i]
结尾的最长连续不重复子序列,长度为i - j + 1
, 将这一长度与r
的较大者更新给r
。
对于每一个i
,如何确定j的位置:由于[j, i - 1]
是前一步得到的最长连续不重复子序列,所以如果[j, i]
中有重复元素,一定是a[i]
,因此右移j
直到a[i]
不重复为止(由于[j, i - 1]
已经是前一步的最优解,此时j只可能右移以剔除重复元素a[i],不可能左移增加元素,因此,j
具有“单调性”、本题可用双指针降低复杂度)。
用数组st[]
记录子序列a[j ~ i]
中各元素出现次数,遍历过程中对于每一个i
有四步操作:输入a[i]
-> 将a[i]
出现次数s[a[i]]
加1 -> 若a[i]重复则右移j(s[a[j]]
要减1) -> 确定j
及更新当前长度i - j + 1
给r
。
注意细节:
当a[i]
重复时,先把a[j]
次数减1,再右移j
。
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
int res;
int a[N],st[N];
int main()
{
int n;
cin >> n;
for(int i = 0, j = 0; i < n; i ++)
{
cin >> a[i];
st[a[i]] ++;
while(st[a[i]] > 1)
{
st[a[j]] --;
j ++;
}
res = max(res, i - j + 1);
}
cout << res;
return 0;
}
判断子序列 传送门
给定一个长度为 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
1.j指针用来扫描整个b数组,i
指针用来扫描a数组。若发现a[i]==b[j]
,则让i
指针后移一位。
2.整个过程中,j
指针不断后移,而i
指针只有当匹配成功时才后移一位,若最后若i==n
,则说明匹配成功。
#include<iostream>
using namespace std;
const int N=100010;
int a[N],b[N];
int main()
{
int n, m;
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;
for(int j = 0; j < m; j ++)
if(a[i] == b[j] && i < n) i ++;
if(i == n) puts("Yes");
else puts("No");
return 0;
}
回文平方 传送门
回文数是指数字从前往后读和从后往前读都相同的数字。
例如数字 12321 就是典型的回文数字。
现在给定你一个整数 B,请你判断 1∼300 之间的所有整数中,有哪些整数的平方转化为 B 进制后,其 B 进制表示是回文数字。
输入格式
一个整数 B。
输出格式
每行包含两个在 B 进制下表示的数字。
第一个表示满足平方值转化为 B 进制后是回文数字那个数,第二个数表示第一个数的平方。
所有满足条件的数字按从小到大顺序依次输出。
数据范围
2≤B≤20,
对于大于 9 的数字,用 A 表示 10,用 B 表示 11,以此类推。
输入样例:
10
输出样例:
1 1
2 4
3 9
11 121
22 484
26 676
101 10201
111 12321
121 14641
202 40804
212 44944
264 69696
需要进制转换的知识
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 30;
char get(int x) //将x转换为字符形式
{
if(x <= 9) return x + '0';
return x - 10 + 'A';
}
string base(int n,int b) //将n转换为b进制,返回对应字符串
{
string num;
while(n) num += get(n % b),n /= b;
reverse(num.begin(),num.end()); //翻转回高位在左
return num;
}
bool check(string s) //检查s是否是回文
{
for(int i = 0, j = s.size() - 1; i < s.size(); i ++, j --)
if(s[i] != s[j]) return false;
return true;
}
int main()
{
int b;
cin >> b;
for(int i = 1; i <= 300; i ++)
{
string num = base(i*i,b);
if(check(num))
cout << base(i,b) << ' ' << num << endl;
}
return 0;
}
归并排序 传送门
给定你一个长度为 n 的整数数列。
请你使用归并排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。
输入格式
输入共两行,第一行包含整数 n。
第二行包含 n 个整数(所有整数均在 1∼109 范围内),表示整个数列。
输出格式
输出共一行,包含 n 个整数,表示排好序的数列。
数据范围
1≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5
典型的双指针运用
#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int a[N],tmp[N];
void merge_sort(int a[],int l,int r)
{
if(l>=r) return;
int mid = (l + r) / 2;
merge_sort(a,l,mid),merge_sort(a,mid+1,r);
int i = l, j = mid + 1, k = 0;
while(i <= mid && j <= r)
{
if(a[i] < a[j]) tmp[k ++] = a[i ++];
else tmp[k ++] = a[j ++];
}
while(i <= mid) tmp[k ++] = a[i ++];
while(j <= r) tmp[k ++] = a[j ++];
for(int i = l, j = 0; i <= r; i ++, j ++) a[i] = tmp[j];
}
int main()
{
int n;
scanf("%d",&n);
for(int i = 0; i < n; i ++) scanf("%d",&a[i]);
merge_sort(a,0,n-1);
for(int i = 0; i < n; i ++) printf("%d ",a[i]);
return 0;
}
双指针在二分当中运用十分广,笔者在此不再赘述,下面有笔者的二分的博客链接
二分查找
双指针的运用还有很多,笔者目前入坑尚浅,后续会继续补充~
欢迎点赞与评论~