院版每日一题(1) 快速寻找满足条件的两个数

能否快速找出一个数组中的两个数字,让这两个数字之和等于一个给定的值,

为了简化起见,我们假设这个数组中肯定存在至少一组符合要求的解。 
假如有如下的两个数组: 
5,6,1,4,7,9,8 
给定Sum= 10 
1,5,6,7,8,9 
给定Sum= 10

 

学长整理大答案(厉害死了)

解法一 
一个直接的解法就是穷举:从数组中任意取出两个数字,计算两者之和是否为
给定的数字。 
显然其时间复杂度为N(n-1)/2即O(N^2)。这个算法很简单,写起来也很容
易,但是效率不高。一般在程序设计里面,要尽可能降低算法的时间和空间复杂度,
所以需要继续寻找效率更高的解法。

解法二 
求两个数字之和,假设给定的和为Sum。一个变通的思路,就是对数组中的每个
数字arr[i]都判别Sum-arr[i]是否在数组中,这样,就变通成为一个查找的算法。 
在一个无序数组中查找一个数的复杂度是O(N),对于每个数字arr[i],都需要
查找对应的Sum-arr[i]在不在数组中,很容易得到时间复杂度还是O(N^2)。这和最
原始的方法相比没有改进。但是如果能够提高查找的效率,就能够提高整个算法的
效率。怎样提高查找的效率呢? 
学过编程的人都知道,提高查找效率通常可以先将要查找的数组排序,然后用
二分查找等方法进行查找,就可以将原来O(N)的查找时间缩短到O(log2N),这
样对于每个arr[i],都要花O(log2N)去查找对应的Sum-arr[i]在不在数组中,总的时
间复杂度降低为N* log2N。当让将长度为N的数组进行排序本身也需要O(N*log2N)
的时间,好在只须要排序一次就够了,所以总的时间复杂度依然是O(N*log2N)。
这样,就改进了最原始的方法。 
到这里,有的读者可能会更进一步地想,先排序再二分查找固然可以将时间从O
(N^2)缩短到O(N*log2N),但是还有更快的查找方法:hash表。因为给定一个
数字,根据hash表映射查找另一个数字是否在数组中,只需要O(1)时间。这样的
话,总体的算法复杂度可以降低到O(N),但这种方法需要额外增加O(N)的hash
表存储空间。某些情况下,用空间换时间也不失为一个好方法。

解法三 
还可以换个角度来考虑问题,假设已经有了这个数组的任意两个元素之和的有
序数组(长为N^2)。那么利用二分查找法,只需用O(2*log2N)就可以解决这个
问题。当然不太可能去计算这个有序数组,因为它需要O(N^2)的时间。但这个思
考仍启发我们,可以直接对两个数字的和进行一个有序的遍历,从而降低算法的时
间复杂度。 
首先对数组进行排序,时间复杂度为(N*log2N)。 
然后令i = 0,j = n-1,看arr[i] + arr[j] 是否等于Sum,如果是,则结束。如果小于
Sum,则i = i + 1;如果大于大于Sum,则 j = j – 1。这样只需要在排好序的数组上遍
历一次,就可以得到最后的结果,时间复杂度为O(N)。两步加起来总的时间复杂
度O(N*log2N),下面这个程序就利用了这个思想,代码如下所示:

然后由猛犸也钻地大神提供的代码: 

 
pair<int,int> gao(int n, int a[], int x){ 
if(!is_sorted(a, a + n)) sort(a, a + n); 
for(int i = 0, j = n - 1; i < n; i++){ 
    while(a[i] + a[j] > x && j > 0) j--; 
    if(a[i] + a[j] == x) return {i, j}; 
} 
return NO_SOLUTION; 
} 

 

 

刚开始一直无法理解这样一定可以找到这个和吗?难道不会漏掉了解的位置。可
以这么理解,假如排好序后的数组为1,3,6,a,9,12,17,28,b,35,46 ,那么i最初指向1的位
置,j最初指向46的位置,比如所求的是Sum=a+b,a<b,a和b在数组中的某位置上。
那么i和j在变化过程中,只考虑i遇到了a或者j遇到了b的时候,必定有一个先遇到,
比如i遇到了a,那么这个时候j必定还没指到b,故这是j指到的值比b大,从而j减小直
到b位置。同理若j先指到了b位置,那么i必定还没指到a(这是我们的前提),然后i现
在指到的值比a小,故i增加,直到a位置。

扩展问题 
1、如果把这个问题中的“两个数字”改成“三个数字”或“任意个数字”时,你的解是什
么呢? 
三个数字:首先还是先对数组进行排序,然后从 i=0到 n-1 进行遍历,遍历arr[i]时,在
调用上面的函数getSumNum(arr , Sum-arr[i])即可。 
任意m个数字的想法: 
首先数组排序,然后从 i=0到n-1 个元素遍历,遍历arr[i]时,在剩下的n-1个元素中调
用 getSumNum(arr,Sum-arr[i]),此时为求 m-1 个元素和为 Sum-arr[i];接下来,同样的方法,

从 j=0 到 n-2 个元素遍历,遍历 arr[j]时在 arr 上递归调用 getSumNum(arr,Sum-arr[i]-arr[j]),
此时为求 m-2 个元素和为 Sum-arr[i]-arr[j];依次递归,直到为求 2 个元素和为 Sum-?-?-?...
时为止。 
不论是求 3 个数字还好是 m 个数字,总是能比较穷举法少一个数量级 n,比先排序然
后二分查找求Sum-arr[i]也要快。

 

 

posted on 2012-05-22 12:51  bingwenst  阅读(925)  评论(0编辑  收藏  举报

导航