问题:
1. 快速找出一个数组中的两个数,让这两个数之和等于一个给定的值。
2. 快速找出一个数组中的三个数,让这三个数之和等于一个给定的值。
1. 解法:算法复杂度为O(nlogn)。先用快速排序对数组排序,让后用双指针(双索引)法对排序好的数组进行反向遍历,并且遍历的方向不变。(若是计算两个数的和,则初始化为i=0,j=n-1,若是计算两个数的差,则初始化为i=0,j=1)
之所以这样遍历方式能成功,是因为排序后,若ai+aj<sum,则ai+ak<sum(k=i,i+1…j),而i之前j之后的情况已遍历过,所以只有i++才可能有等号的情况;若ai+aj>sum,则ak+aj>sum(k=i,i+1…j),而i之前j之后的情况已遍历过,所以只有j–才可能有等号的情况。
#include <iostream>
#include <algorithm>
using namespace std;
#define MAXN 1001
int A[MAXN];
// 总的时间复杂度为O(nlogn)
int main()
{
int n, sum, i, j;
cin >> n >> sum;
for (i=0; i<n; i++)
cin >> A[i];
// 快速排序O(nlogn)
sort(A, A+n);
// 双索引反向遍历
i=0; j=n-1;
// 每个数只能用一次(若每个数可以用多次,则用<=)
while (i<j)
{
int plus = A[i]+A[j];
if (plus == sum)
{
printf("(%d,%d) ",A[i],A[j]);
i++, j--;
}
else if (plus < sum)
i++;
else
j--;
}
}
2. 解法:时间复杂度为O(n^2)。如果数组已排序,利用解法1的双指针遍历法,可以在O(n)的时间内找到两个数之和等于一个給定的数。我们假设找到的三个数ai,aj,ak有ai<=aj<=ak,要让ai+aj+ak=sum,也就是要ai+aj=sum-ak,设subsum=sum-ak,很容易发现subsum的值只有n个,而确定ai+aj=subsum中的ai,aj只需要O(n)的时间,所以总的时间复杂度为O(nlogn+n*n)=O(n^2)
#include <iostream>
#include <algorithm>
using namespace std;
#define MAXN 1001
int A[MAXN];
int main()
{
int n, sum, i, j, k;
cin >> n >> sum;
for (i=0; i<n; i++)
cin >> A[i];
sort(A, A+n);
for (k=0; k<n; k++)
{
i=0; j=k-1;
if (k==i) i++;
if (k==j) j--;
int subsum = sum-A[k];
while (i<j)
{
int plus = A[i]+A[j];
if (plus == subsum)
{
printf("(%d,%d,%d) ",A[i],A[j],A[k]);
i++, j--;
}
else if (plus < subsum)
i++;
else
j--;
if (k==i) i++;
if (k==j) j--;
}
}
}
若允许每个数被多次选取,只需稍微改下代码,具体如下:
#include <iostream>
#include <algorithm>
using namespace std;
#define MAXN 1001
int A[MAXN];
int main()
{
int n, sum, i, j, k;
cin >> n >> sum;
for (i=0; i<n; i++)
cin >> A[i];
sort(A, A+n);
for (k=0; k<n; k++)
{
i=0; j=k;
//if (k==i) i++;
//if (k==j) j--;
int subsum = sum-A[k];
while (i<=j)
{
int plus = A[i]+A[j];
if (plus == subsum)
{
printf("(%d,%d,%d) ",A[i],A[j],A[k]);
i++, j--;
}
else if (plus < subsum)
i++;
else
j--;
//if (k==i) i++;
//if (k==j) j--;
}
}
}