浅谈双指针

常用双指针技巧可以分为两类,一类是「快慢指针」,一类是「左右指针」。笔者在本章只讲左右指针,主要解决数组(或者字符串)中的问题,比如二分查找,处理子序列。

左右指针

将给定的英文字符串进行反转。我们可以使用一头一尾的双指针。
例如: 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开始 从前往后遍历
jm - 1开始 从后向前遍历
由于数据保证有唯一解,双指针是可行的。
时间复杂度最坏为 O(n + m),比双重for循环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 + 1r
注意细节:
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;
}

双指针在二分当中运用十分广,笔者在此不再赘述,下面有笔者的二分的博客链接
二分查找

双指针的运用还有很多,笔者目前入坑尚浅,后续会继续补充~
欢迎点赞与评论~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值