http://tech-wonderland.net/blog/summary-of-ksum-problems.html
前言:
做過leetcode的人都知道, 裡面有2sum, 3sum(closest), 4sum等問題, 這些也是面試裡面經典的問題, 考察是否能夠合理利用排序這個性質, 一步一步得到高效的演算法. 經過總結, 本人覺得這些問題都可以使用一個通用的K sum求和問題加以概括消化, 這裡我們先直接給出K Sum的問題描述和演算法(遞迴解法), 然後將這個一般性的方法套用到具體的K, 比如leetcode中的2Sum, 3Sum, 4Sum問題. 同時我們也給出另一種雜湊演算法的討論. leetcode求和問題描述(K sum problem):
K sum的求和問題一般是這樣子描述的:給你一組N個數字(比如 vector<int> num), 然後給你一個常數(比如 int target) ,我們的goal是在這一堆數裡面找到K個數字,使得這K個數位和等於target。 注意事項(constraints):
注意這一組數字可能有重複項:比如 1 1 2 3 , 求3sum, 然後 target = 6, 你搜的時候可能會得到 兩組1 2 3, 1 2 3,1 來自第一個1或者第二個1, 但是結果其實只有一組,所以最後結果要去重。 K Sum求解方法, 適用leetcode 2Sum, 3Sum, 4Sum:
方法一: 暴力,就是枚舉所有的K-subset, 那麼這樣的複雜度就是 從N選出K個,複雜度是O(N^K)
方法二: 排序,這個演算法可以考慮最簡單的case, 2sum,這是個經典問題,方法就是先排序,然後利用頭尾指標找到兩個數使得他們的和等於target, 這個2sum演算法網上一搜就有,這裡不贅述了,給出2sum的核心代碼:
//2 sumint i = starting; //頭指標int j = num.size() - 1; //尾指標while(i < j) { int sum = num[i] + num[j]; if(sum == target) { store num[i] and num[j] somewhere; if(we need only one such pair of numbers) break; otherwise do ++i, --j; } else if(sum < target) ++i; else --j;}
2sum的演算法複雜度是O(N log N) 因為排序用了N log N以及頭尾指標的搜尋是線性,所以總體是O(N log N),好了現在考慮3sum, 有了2sum其實3sum就不難了,這樣想:先取出一個數,那麼我只要在剩下的數字裡面找到兩個數字使得他們的和等於(target – 那個取出的數)就可以了吧。所以3sum就退化成了2sum, 取出一個數字,這樣的數字有N個,所以3sum的演算法複雜度就是O(N^2 ), 注意這裡複雜度是N平方,因為你排序只需要排一次,後面的工作都是取出一個數字,然後找剩下的兩個數字,找兩個數字是2sum用頭尾指標線性掃,這裡很容易錯誤的將複雜度算成O(N^2 log N),這個是不對的。我們繼續的話4sum也就可以退化成3sum問題,那麼以此類推,K-sum一步一步退化,最後也就是解決一個2sum的問題,K sum的複雜度是O(n^(K-1))。 這個界好像是最好的界了,也就是K-sum問題最好也就能做到O(n^(K-1))複雜度,之前有看到過有人說可以嚴格數學證明,這裡就不深入研究了。 K Sum (2Sum, 3Sum, 4Sum) 演算法最佳化(Optimization):
這裡講兩點,第一,注意比如3sum的時候,先整體排一次序,然後枚舉第三個數位時候不需要重複, 比如排好序以後的數字是 a b c d e f, 那麼第一次枚舉a, 在剩下的b c d e f中進行2 sum, 完了以後第二次枚舉b, 只需要在 c d e f中進行2sum好了,而不是在a c d e f中進行2sum, 這個大家可以自己體會一下,想通了還是挺有協助的。第二,K Sum可以寫一個遞迴程式很優雅的解決,具體大家可以自己試一試。寫遞迴的時候注意不要重複排序就行了。 Hash解法(Other):
其實比如2sum還是有線性解法的,就是用hashmap, 這樣你check某個值存在不存在就是常數時間,那麼給定一個sum, 只要線性掃描, 對每一個number判斷sum – num存在不存在就可以了。注意這個演算法對有重複元素的序列也是適用的。比如 2 3 3 4 那麼hashtable可以使 hash(2) = 1; hash(3) = 1, hash(4) =1其他都是0, 那麼check的時候,掃到兩次3都是check sum – 3在不在hashtable中,注意最後返回所有符合的pair的時候也還是要去重。這樣子推廣的話 3sum 其實也有O(N^2)的類似hash演算法,這點和之前是沒有提高的,但是4sum就會有更快的一個演算法。 4sum的hash演算法:
O(N^2)把所有pair存入hash表,並且每個hash值下面可以跟一個list做成map, map[hashvalue] = list,每個list中的元素就是一個pair, 這個pair的和就是這個hash值,那麼接下來求4sum就變成了在所有的pair value中求 2sum,這個就成了線性演算法了,注意這裡的線性又是針對pair數量(N^2)的線性,所以整體上這個演算法是O(N^2),而且因為我們掛了list, 所以只要符合4sum的我們都可以找到對應的是哪四個數字。
關於hash的解法我研究還不是很多,以後要是有更深入的研究再更新。 結束語:
這篇文章主要想從一般的K sum問題的角度總結那些比較經典的求和問題比如leetcode裡面的2sum, 3sum(closest), 4sum等問題, 文章先直接給出K Sum的問題描述和演算法(遞迴解法), 然後將這個一般性的方法套用到具體的K, 比如leetcode中的2Sum, 3Sum, 4Sum問題. 同時我們也給出另一種雜湊演算法的討論. 那麼這篇文章基本上還是自己想到什麼寫什麼,有疏忽不對的地方請大家指正,也歡迎留言討論,如果需要原始碼,請留言或者發郵件到info@tech-wonderland.net