程序员面试100题之十:快速寻找满足条件的两个数
???? 假如有如下的兩個(gè)數(shù)組,如圖所示:
??? 5,6,1,4,7,9,8
??? 給定Sum= 10
??? 1,5,6,7,8,9
??? 給定Sum= 10
?? 分析與解法
??? 這個(gè)題目不是很難,也很容易理解。但是要得出高效率的解法,還是需要一番思考的。
??? 解法一
???? 一個(gè)直接的解法就是窮舉:從數(shù)組中任意取出兩個(gè)數(shù)字,計(jì)算兩者之和是否為給定的數(shù)字。
???? 顯然其時(shí)間復(fù)雜度為N(N-1)/2即O(N^2)。這個(gè)算法很簡(jiǎn)單,寫起來也很容易,但是效率不高。一般在程序設(shè)計(jì)里面,要盡可能降低算法的時(shí)間和空間復(fù)雜度,所以需要繼續(xù)尋找效率更高的解法。
???? 解法二
???? 求兩個(gè)數(shù)字之和,假設(shè)給定的和為Sum。一個(gè)變通的思路,就是對(duì)數(shù)組中的每個(gè)數(shù)字arr[i]都判別Sum-arr[i]是否在數(shù)組中,這樣,就變通成為一個(gè)查找的算法。
???? 在一個(gè)無序數(shù)組中查找一個(gè)數(shù)的復(fù)雜度是O(N),對(duì)于每個(gè)數(shù)字arr[i],都需要查找對(duì)應(yīng)的Sum-arr[i]在不在數(shù)組中,很容易得到時(shí)間復(fù)雜度還是O(N^2)。這和最原始的方法相比沒有改進(jìn)。但是如果能夠提高查找的效率,就能夠提高整個(gè)算法的效率。怎樣提高查找的效率呢?
???? 學(xué)過編程的人都知道,提高查找效率通??梢韵葘⒁檎业臄?shù)組排序,然后用二分查找等方法進(jìn)行查找,就可以將原來O(N)的查找時(shí)間縮短到O(log2N),這樣對(duì)于每個(gè)arr[i],都要花O(log2N)去查找對(duì)應(yīng)的Sum-arr[i]在不在數(shù)組中,總的時(shí)間復(fù)雜度降低為N* log2N。當(dāng)讓將長(zhǎng)度為N的數(shù)組進(jìn)行排序本身也需要O(N*log2N)的時(shí)間,好在只須要排序一次就夠了,所以總的時(shí)間復(fù)雜度依然是O(N*log2N)。這樣,就改進(jìn)了最原始的方法。
???? 到這里,有的讀者可能會(huì)更進(jìn)一步地想,先排序再二分查找固然可以將時(shí)間從O(N^2)縮短到O(N*log2N),但是還有更快的查找方法:hash表。因?yàn)榻o定一個(gè)數(shù)字,根據(jù)hash表映射查找另一個(gè)數(shù)字是否在數(shù)組中,只需要O(1)時(shí)間。這樣的話,總體的算法復(fù)雜度可以降低到O(N),但這種方法需要額外增加O(N)的hash表存儲(chǔ)空間。某些情況下,用空間換時(shí)間也不失為一個(gè)好方法。
???? 解法三
???? 還可以換個(gè)角度來考慮問題,假設(shè)已經(jīng)有了這個(gè)數(shù)組的任意兩個(gè)元素之和的有序數(shù)組(長(zhǎng)為N^2)。那么利用二分查找法,只需用O(2*log2N)就可以解決這個(gè)問題。當(dāng)然不太可能去計(jì)算這個(gè)有序數(shù)組,因?yàn)樗枰狾(N^2)的時(shí)間。但這個(gè)思考仍啟發(fā)我們,可以直接對(duì)兩個(gè)數(shù)字的和進(jìn)行一個(gè)有序的遍歷,從而降低算法的時(shí)間復(fù)雜度。
????? 首先對(duì)數(shù)組進(jìn)行排序,時(shí)間復(fù)雜度為(N*log2N)。
????? 然后令i = 0,j = n-1,看arr[i] + arr[j] 是否等于Sum,如果是,則結(jié)束。如果小于Sum,則i = i + 1;如果大于Sum,則 j = j – 1。這樣只需要在排好序的數(shù)組上遍歷一次,就可以得到最后的結(jié)果,時(shí)間復(fù)雜度為O(N)。兩步加起來總的時(shí)間復(fù)雜度O(N*log2N),下面這個(gè)程序就利用了這個(gè)思想,代碼如下所示:
int getSumNum(int[] arr,int Sum), //arr為數(shù)組,Sum為和 {int i,j;for(i = 0, j = n-1; i < j ; ){if(arr[i] + arr[j] == Sum)return ( i , j );else if(arr[i] + arr[j] < Sum)i++;elsej--;}return ( -1 , -1 ); }????? 它的時(shí)間復(fù)雜度是O(N)。
????? 剛開始一直無法理解這樣一定可以找到這個(gè)和嗎?難道不會(huì)漏掉了解的位置。可以這么理解,假如排好序后的數(shù)組為1,3,6,a,9,12,17,28,b,35,46? ,那么i最初指向1的位置,j最初指向46的位置,比如所求的是Sum=a+b,a<b,a和b在數(shù)組中的某位置上。那么i和j在變化過程中,只考慮i遇到了a或者j遇到了b的時(shí)候,必定有一個(gè)先遇到,比如i遇到了a,那么這個(gè)時(shí)候j必定還沒指到b,故這時(shí)j指到的值比b大,從而j減小直到b位置。同理若j先指到了b位置,那么i必定還沒指到a(這是我們的前提),然后i現(xiàn)在指到的值比a小,故i增加,直到a位置。
?
?擴(kuò)展問題
1、如果把這個(gè)問題中的“兩個(gè)數(shù)字”改成“三個(gè)數(shù)字”或“任意個(gè)數(shù)字”時(shí),你的解是什么呢?
三個(gè)數(shù)字:首先還是先對(duì)數(shù)組進(jìn)行排序,然后從i=0到n-1進(jìn)行遍歷,遍歷arr[i]時(shí),在調(diào)用上面的函數(shù)getSumNum(arr , Sum-arr[i])即可。
任意m個(gè)數(shù)字的想法:
????? 首先還是先對(duì)數(shù)組進(jìn)行排序,然后從i=0到n-1個(gè)元素遍歷,遍歷arr[i]時(shí),在剩下的n-1個(gè)元素中調(diào)用getSumNum(arr,Sum-arr[i]),此時(shí)為求m-1個(gè)元素和為Sum-arr[i];接下來,同樣的方法,從j=0到n-2個(gè)元素遍歷,遍歷arr[j]時(shí)在arr上遞歸調(diào)用getSumNum(arr,Sum-arr[i]-arr[j]),此時(shí)為求m-2個(gè)元素和為Sum-arr[i]-arr[j];依次遞歸,直到為求2個(gè)元素和為Sum-?-?-?...時(shí)為止。
不論是求3個(gè)數(shù)字還好是m個(gè)數(shù)字,總是能比較窮舉法少一個(gè)數(shù)量級(jí)n,比先排序然后二分查找求Sum-arr[i]也要快。
?
總結(jié)
以上是生活随笔為你收集整理的程序员面试100题之十:快速寻找满足条件的两个数的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 程序员面试100题之九:求子数组的最大和
- 下一篇: 程序员面试100题之十一:数组循环移位