【數組&雙指針】leetcode4. 尋找兩個正序數組的中位數【困難, 未完】

【題目】

給定兩個大小分別爲m和n的正序(從小到大)數組 nums1 和 nums2。請你找出並返回這兩個正序數組的中位數 。

算法的時間複雜度應該爲O(log (m+n)) 。

 

示例 1:

輸入:nums1 = [1,3], nums2 = [2]
輸出:2.00000
解釋:合併數組 = [1,2,3] ,中位數 2


示例 2:

輸入:nums1 = [1,2], nums2 = [3,4]
輸出:2.50000
解釋:合併數組 = [1,2,3,4] ,中位數 (2 + 3) / 2 = 2.5
 

提示:

nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-106 <= nums1[i], nums2[i] <= 106

【分析】

https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/si-wei-dao-tu-zheng-li-3chong-fang-fa-ch-osfo/

 

 

藉此review一下中位數問題

1. 常規思想的改進:假合併/奇偶合並

本題的常規思想還是挺簡單的:使用歸併的方式,合併兩個有序數組,得到一個大的有序數組。大的有序數組的中間位置的元素,即爲中位數。但是這種思路的時間複雜度是O(m+n),空間複雜度是O(m+n),都比較高。

因此我們必須想辦法將算法進行優化,這裏先介紹一種簡單的優化方式,就是假合併,即我們並不需要真的合併兩個有序數組,只要找到中位數的位置即可。

它的思想並不複雜,由於兩個數組的長度已知,因此中位數對應的兩個數組的下標之和也是已知的。維護兩個指針,初始時分別指向兩個數組下標0的位置,每次將指向較小值的指針向後移動一位(若一個指針已達到數組末尾,則只需要移動另一個數組的指針),直到達到中位數的位置。

通過這種假合併的方式,我們可以成功將空間複雜度優化到O(1),但是對於時間複雜度並未有什麼優化。講解這個方法的目的並不是讓大家掌握該方法,而是爲了讓大家掌握此方法的一些巧妙的優化方式。

該方法看似容易,真正寫代碼的時候還是很有挑戰的,不僅需要考慮奇偶問題,更要考慮一個數組遍歷結束後的各種邊界問題,其實很多問題就是難在了對於邊界的處理上。

此方法的一個優化點就是將奇偶兩種情況合併到了一起,具體思想如下:

 

 

這種思想是很有必要的, 對於數組來說, 我們經常會遇到奇偶的兩種情況處理, 如果想辦法將他們合併在一起, 那代碼寫起來就是非常順暢和整潔.

另一種合併的思想是: 我們可以在奇數的時候, 在末尾等處添加一個佔位符#等, 這樣也是可以將奇數合併成偶數的情況的.

此方法的另一個優化點就是 通過在if條件中加入大量的限制條件, 從而實現了對於各種邊界問題的處理, 這也是一種很重要的思想.

 

 

 

 

此方法的時間複雜度相對於下面兩種思想還是太高了, 大家不用特意掌握此方法, 但是這兩個優化的思想還是很重要的, 要好好的理解一下.

接下來我們就來詳細講解兩個時間複雜度超低的算法代碼思想。

2. 尋找第k小數 代碼詳解

這個思想最難的點在於 三種特殊情況的處理,我們能否想到這三種情況,並將它們完美融入到代碼之中。

其實,不需要將兩個數組真的合併,只需要找到中位數在哪裏就可以了。

開始的思路或許是寫一個循環,然後裏面判斷是否到了中位數的位置,到了就返回結果,但這裏對偶數和奇數的分類會很麻煩。當其中一個數組遍歷完後,出了for循環對邊界的判斷也會分幾種情況。總體來說,雖然複雜度不影響,但代碼看起來會很亂。

首先是怎麼將奇數和偶數的情況合併一下。

用len表示合併後數組的長度,如果是奇數,我們需要知道第(len + 1)/ 2 個數就可以了,如果遍歷的話需要遍歷int(len/2) / + 1次。如果是偶數,我們需要知道第len/2 and (len/2) + 1個數,也是需要遍歷 (len/2) + 1次。所以遍歷的話,奇數和偶數都是需要遍歷 (len/2) + 1次。

返回中位數的話,奇數需要最後一次遍歷的結果就可以了,偶數需要最後一次和上一次遍歷的結果。所以我們用兩個變量left和right,right保存當前循環的結果,在每一次循環前將right的值賦給left。這樣在最後一次循環的時候,left將得到right的值,也就是上一次循環的結果,接下來right更新爲最後一次的結果。

循環中該怎麼寫?什麼時候A數組後移,什麼時候B數組後移。用aStart和bStart分別表示當前指向A數組和B數組的位置。如果aStart還沒有遍歷到最後並且此時A數組位置的數字小於B數組位置的數字,那麼aStart就可以後移了。也就是aStart < m && A[aStart] < B[bStart]。

但如果B數組此刻已經沒有數字了,繼續取數字B[bStart]則會越界,所以此時需要判斷下bStart是否大於B數組長度:

aStart < m && bStart < n || A[aStart] < B[bStart] 

如果產生越界,|| 後面的就不會執行了。

時間複雜度:遍歷 len/2+1 次,len=m+n,所以時間複雜度依舊是 O(m+n)。

空間複雜度:我們申請了常數個變量,也就是 m,n,len,left,right,aStart,bStart 以及 i。

總共 8 個變量,所以空間複雜度是 O(1)。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章