双指针法与其说是一种算法,不如说是一种思想。
双指针经典题目--在有序数列a中找和为s的两个数。
例如a为 -5,-3,2,4,6,10 求和为1的两个数。答案是【-5,6】【-3,4】
我们用两个指针 l,r 分别指向数列的左右端点。
如果当前 a[l]+a[r]>s 那么我们只能通过 r-- 来使得其和变小。
同样,如果 a[l]+a[r]<s 我们只能通过 l++ 使和变大。
a[l]+a[r]==s 找到一组解。这时候也要改变一下 l 或 r ,让程序继续执行下去,直至 l==r 指针碰撞。
双指针法利用了数据的有序性,使得决策可以通过当前状态推出,提高了效率。
int l=0;
int r=n-1;
while (l!=r){
int s=a[l]+a[r];
if (s<target) l++;
else if (s>target) r--;
else {
cout<<a[l]<<" "<<a[r]<<endl;
l++;
}
}
更多例题
1.三数之和
https://leetcode-cn.com/problems/3sum/
排序,确定好第一个数,然后双指针取剩下的两个数。
因为要求方案不重复,所以指针必须是跳跃的,要注意越界问题。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& a) {
sort(a.begin(),a.end());
int n=a.size();
vector<int> t(3,0);
vector<vector<int>> ans;
int i=0;
while (i<n-1){
if (a[i]>0) break; //最下的是正数 那么后边就不需要考虑了
int tar=-a[i];
//双指针法在 i+1~n-1中找tar
int l=i+1;
int r=n-1;
while (l<r){ //l的跳跃增长会使得结束条件不一定是l==r
int sum=a[l]+a[r];
if (sum<tar) l++;
else if (sum>tar) r--;
else{
t.clear();
t.push_back(a[i]);
t.push_back(a[l]);
t.push_back(a[r]);
ans.push_back(t);
while (l<n-1 && a[l]==a[l+1]) l++; l++; //a[l]唯一
}
}
while(i<n-1 && a[i]==a[i+1]) i++; i++; //a[i]唯一
}
return ans;
}
};
2.多多吃饭
最近,碰到了一个这样的题目,也用到了双指针的思想。
有N种午饭和M种晚饭,每种饭都有热量值X和美味值Y,并且每顿饭最多吃一种,问在美味值之和不小于T的情况下,最少的热量摄入X。
原题是拼多多秋招第三题:https://blog.csdn.net/qq_21989927/article/details/107751197
如果饭A的热量值大于B的热量值,并且A的美味值小于B的美味值,那么这个饭A是肯定没有用的,所以我们可以直接不考虑饭A,所以排序完成后,应该是x和y都在递增的情况。
至此,再找出一组或一个y>=T且x的最小值即可。
午饭和晚饭被分为两组,指针l从小到大对午饭遍历,指针r从大到小对晚饭遍历。
我们发现,当前的组合如果满足了T,那么为了求得可能更优的解,我们必须要 r-- 用更小的晚饭去匹配当前的午饭。
如果当前的组合不满足T,那么晚饭再怎么选也不能满足(因为r-- 晚饭更小),只能午饭指针 l++。
所以这个题目也可以用双指针完成,复杂度O(n)。
看来双指针有更广泛的应用场景,如果当前的状态判断可以明确决定下一步的判断,并且保证了下一步判断的延续性,数据本身的有序性又保证了这个性质,有点儿像递推【逃】