这篇博客将用两个经典的问题,带各位熟悉双指针的应用,更加深刻的理解双指针的原理和作用,适合刚刚学习的新手来加深理解并做练习。
问题:
给定一个数组,和一个目标和,要求从这个数组中选取两数之和为此目标和,输出这两个数的地址。
思路:
1.枚举,如果从n个数中选取两个数,应该有n!/2!(n-2)!中组合,一一枚举并验证是一个常见思路,但这个算法的时间复杂度是O(n^2)
2.换个思路,我们入果要得到目标和,那我们可以从一中和的可能性不断向这个和去靠近,如果偏小,我们就换一个较大数,如果偏大,我们就换一个较小数。在上面叙述中,我们很容易感受到,要想实现大小之间的替换,首先要对数组进行排序。然后引入双指针。左指针i的起始位置在最左端,右指针j的起始位置在最右端。如果a[i]+a[j]<sum,说明现在得到的值偏小,需要得到一个较大值,那我们就需要将左指针移动,即i++;如果a[i]+a[j]>sum,说明现在得到的值偏小,需要得到一个较大值,那我们就需要将左指针移动,即j--;
3.以上是我们解决问题的基本思路,不过还有一些我们需要注意的。那就是题目要求我们返回的是地址,但在排序之后,我们的数组顺序已经和原来不同,那么地址也就不相同,因此我们要形成一个从数值到地址的映射,可以在一开始便存一个临时数组来记录原数组的顺序。得到答案后将它映射回去,找到对应在原数组中的地址。到这里,我们的思路就已经很完备了。
代码实现:
#include <iostream>
#include <algorithm>
using namespace std;
int a[1000], tmp[1000];
int A, B;
int sum;
int ans1, ans2;
int check(int l, int r) { //双指针查找组成目标和的两个数值
int i = l, j = r;
if (a[i] + a[j] < sum) {
i++;//移动左指针
check(i, j);
}
if (a[i] + a[j] > sum) {
j--;//移动右指针
check(i, j);
}
if (a[i] + a[j] == sum)
return A = a[i], B = a[j]; //返回数值
}
int main() {
cin >> sum;
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
tmp[i] = a[i]; //储存数组中每一个数原来的地址
}
sort( a + 1, a + n + 1);
check(1, n);
for (int i = 1; i <= n; i++) {
if (A == tmp[i])
ans1 = i;
if (B == tmp[i])
ans2 = i; //查找数值所对应的地址
}
cout << ans1 << " " << ans2 ;
return 0;
}
备注:在子函数中我们返回的是组成目标和的数值,再在主函数中查找它的地址。
进阶:
现在我们将问题变得更复杂一些,要求选取三个数组成目标和,又该怎么办呢?
思路:
其实这个问题的基础还是上面的两数之和,我们可以想象有三个指针,令最左端的指针不动,那边右边两个形成的问题就与两数之和问题完全相同。这样一来,我们先固定一个指针,将问题转化为双指针问题,在每一个双指针子问题解决之后,再更换固定指针的位置,思路是完全一样的。
总结:
双指针的思路对于一些线性排序上的查找可以大幅的优化时间复杂度,对于初学者来说是一种非常有用的手法,需要多加练习。