演算法之美:你可能想不到的歸併排序的神奇應用

韓子遲發表於2016-05-22

又是一道有意思的題目,Count of Range Sum。(PS:leetcode 我已經做了 190 道,歡迎圍觀全部題解 https://github.com/hanzichi/leetcode

題意非常簡單,給一個陣列,如果該陣列的一個子陣列,元素之和大於等於給定的一個引數值(lower),小於等於一個給定的引數值(upper),那麼這為一組解,求總共有幾組解。

一個非常容易想到的解法是兩層 for 迴圈遍歷子陣列首尾,加起來判斷,時間複雜度 O(n^2)。

交了下 TLE 了,看了下測試資料,陣列長度為 9000,複雜度達到了 8100w,還是蠻大的。其實題目中也說了: A naive algorithm of O(n2) is trivial. You MUST do better than that.

如何將複雜度降到 log 級別?想到了二分的方法。可以將子陣列和轉換成兩個字首陣列和的差,定義陣列 sum, sum[i] 表示陣列前 i 個元素的和,特殊的, sum[0]=0,那麼元素 i 到元素 j 的和可以表示為 sum[j]-sum[i-1]。我們列舉 0 到 nums.length,比如列舉到了 sum[j],我們需要求滿足條件的 i(i

解法似乎呼之而出,用二分維護有序陣列(用 splice 插入),同時用二分找到臨界的資料,一次迭代需要多次二分。二分查詢相關可以看我以前的文章 二分查詢大集合(媽媽再也不用擔心我的二分查詢了)

注意下二分的邊界,程式碼很容易寫出來。

很不幸,還是 TLE 了,究其原因,我覺得應該是呼叫了 n 次 splice 方法。 感覺維護一棵二叉搜尋樹應該是可行的,無奈不會手寫二叉搜尋樹 = =

那麼可行的解法是什麼呢?答案是歸併排序的 “另類使用”。這裡不講歸併排序,關於歸併排序,可見我以前的文章

言歸正傳,首先預處理陣列的字首和,儲存到陣列 sum 中。然後用歸併排序對陣列 sum 進行排序,歸併排序中有一步呼叫 merge 函式,將有序的左陣列和右陣列進行合併,而這時的右陣列中的任一元素在 sum 陣列中的位置正是在左陣列任一元素之後!利用這,我們可以在 merge 前,對 left 陣列和 right 陣列滿足條件的元素進行求解。

這個函式我定義為 getAns:

做完一次歸併排序,每次 left 和 right 陣列合並前進行判斷,就將所有 sum[j]-sum[i](j>i) 的情況進行了判斷,簡直神奇!

完整程式碼參考我的 Github

224ms!Your runtime beats 100.00% of javascript submissions

還是有點小激動

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

演算法之美:你可能想不到的歸併排序的神奇應用

相關文章