在《编程之美》一书的第2.12个题目描述如下:
能否快速找出一个数组中的两个数字,让这个两个数字之和等于一个给定的值,为了简化起见,我们假设这个数组中肯定至少存在一组满足要求的解。
作者给出了3种方案:
[方案一]穷举法:从数组任意取出两个数字,计算两者之和是否为给定的数字。时间复杂度O(N^2),效率较低。
[方案二]将问题变通为一个查找问题:求两个数之和,假定为sum。对于数组中的每个数array[i],判断sum-array[i]是否在数组中。在一个无序数组中查找一个数的复杂度是O(N),对于每个数字都要进行相应的查找,则时间复杂度仍为O(N^2)。为了提高效率,可以相对数组进行排序,然后利用二分查找算法,时间复杂度为O(NlogN)。
[方案三]利用hash表提高查找效率,查找时间为O(1),总的时间复杂度就为O(N)。但是这会使得空间复杂度也为O(N)。
[方案四]先对数组进行排序,然后对数组中的两个数之和进行一个有序的遍历。具体算法:先令i=0,j=n-1,判断array[i]+array[j]是否等于sum,如相等则结束;如小于则i向右移动一个位置,否则j向左移动一个位置。这样时间复杂度为排序O(NlogN)+查找O(N) = O(N(logN+1))
[扩展问题1]:如果将“两个数字”改为“三个数字”或“任意个数字”时,如何求解?
[方案]对于“三个数字”其实就是对于数组中的每个数字array[i]判断数组中的其他数字是否存在两个数字的和为sum-array[i],以此递归下去。
具体算法(C实现):
int index = 0;
bool find(int array[], int n, int start, int m, int sum, int result[]){
assert(m >= 2);
if(n-start < m)
return false;
int i,j;
if(m == 2){
for(i=start,j=n;i<j;){
if(array[i] + array[j] == sum){
result[index++] = array[i];
result[index++] = array[j];
return true;
}
else if(array[i] + array[j] < sum)
i++;
else
j--;
}
return false;
}else{
for(i=start;i<n;i++){
result[index++] = array[i];
if(find(array,n,i+1,m-1,sum - array[i],result)){
return true;
}
index--;
}
return false;
}
return false;
}
[扩展问题2]:找到最接近的解,算法实现(C语言):
void findNearest(int array[], int n, int sum, int result[]){
int i,j;
int minDiff = INT_MAX;
for(i=0,j=n-1;i<j;){
if(array[i]+array[j] == sum){
result[0] = array[i];
result[1] = array[j];
return;
}else{
int diff = array[i]+array[j] -sum;
int tmp_i = array[i];
int tmp_j = array[j];
if(diff > 0)
j--;
else{
i++;
diff = -diff;
}
if(diff < minDiff){
minDiff = diff;
result[0] = tmp_i;
result[1] = tmp_j;
}
}
}
}