算法-尋找兩個有序數組的中位數

原題是力扣上的題目

給定兩個大小爲 m 和 n 的有序數組 nums1 和 nums2。

請你找出這兩個有序數組的中位數,並且要求算法的時間複雜度爲 O(log(m + n))。

你可以假設 nums1 和 nums2 不會同時爲空。

示例 1:

nums1 = [1, 3]
nums2 = [2]

則中位數是 2.0
示例 2:

nums1 = [1, 2]
nums2 = [3, 4]

則中位數是 (2 + 3)/2 = 2.5

題目不算難,但是有個問題,要求時間複雜度爲O(log(m + n)),也就是說,如果我們想先統一排序,再去取中位數的操作是行不通的,因爲那樣的時間複雜度爲O(m+n),我對這個題目思考了很久,如果想要做到O(log(m + n)),那勢必每次循環都需要排除一半的可能,猜到應該是和有序有關,但是基本沒有思路,因爲是有可能存在【1,3,5,7,9】【2,4,6,8】這樣滿足有序的數組,但是我完全想不出來每次操作都排除一半可能的操作方法

想了半天,實在沒有解決思路,就可恥的去看了官方的解法,官方的解法更是讓人頭大,各種數學公式,看的我頭暈眼花,真是讓人頭大,繞過這些鬼話,直接看代碼,終於勉強看懂了
在這裏插入圖片描述
首先讓我們想一想中位數的本質是什麼,就是一個數組中的最中間的數,我們將兩個數組宛若俄羅斯套娃一般排列,中間較小的那個正方形代表較大數組中的全部值,而外面套着的這一圈就是較小數組代表的值,原諒我實在懶得重新畫了,正常來說中間這條線就是中位數
在這裏插入圖片描述
而本題中,在最好的情況下,也確實是這樣,但是最好的情況畢竟也不多見,官方題解的思路也差不多是這樣,如果兩個數組相交的那條線
在這裏插入圖片描述
大於中間這條線,那麼我們可以認爲,這個數顯然不可能是中位數,他太大了,所以我們把較大數組的一部分填充過來,而把較小數組的一部分捨去,相反,我們就認爲這個數太小了,所以我們把較大數組的一部分捨棄過來,而把較小數組的一部分加入,如果不斷操作,最後可能會出現下面這幾種情況
在這裏插入圖片描述
較小的數組都太大了,均不可能滿足中位數的要求,那麼我們只需要根據數組有序的前提,直接得出較大數組[(m+n)/2+1](奇數)就是中位數,相反,如果較小數組擠佔了所有的空間,我們就可以認爲較小數組[(m+n)/2+1](奇數)就是中位數,但是現實中顯然不太可能出現這兩種極端的情況,我們需要考慮一種更復雜的情況,如果較大數組和較小數組互相重疊,也就是我們之前提到的【1,3,5,7,9】【2,4,6,8】的情況,這種情況下,問題顯然複雜的多,會呈現這種情況
在這裏插入圖片描述
中位數可能落在兩個數組任意一個上,我們不能在簡單的說[(m+n)/2+1]就是中位數了,那有沒有辦法呢,其實是有的,比如出現上面這種混亂的情況,我們只需要獲取中位數在哪就行了,有兩種可能,中位數在較小數組的未被排除的最後一位,或是在較大數組的未被排除的最後一位,我們只需要用較小數組的可能數組減去總的可能數組,就可以知道較大數組未被排除的最後一位數,然後獲取這兩個數,哪個數大,那麼這個數就是中位數(奇數),因爲更大的數更接近中位數,最接近的那個數就是中位數本身,官方算法的思路就是如此

public double findMedianSortedArrays(int[] A, int[] B) {
            int m = A.length;
            int n = B.length;
            將a轉爲爲那個較小的數組
            if (m > n) { // to ensure m<=n
                int[] temp = A; A = B; B = temp;
                int tmp = m; m = n; n = tmp;
            }
            imin表示較小數組的最小位置
            imax表示較小數組的最大位置
            halfLen表示中位數出現的最大半徑(即,兩個數組之和的一半,向上取整),若要取(m+n)/2+1需要-1
            int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2;
            while (iMin <= iMax) {
            i表示當前較小數組所佔的可能數
                int i = (iMin + iMax) / 2;
                j表示較大數組所佔的可能數
                int j = halfLen - i;
                說明當前的較大數組的值太大了,需要填充一部分較小數組
                if (i < iMax && B[j-1] > A[i]){
                    iMin = i + 1; // i is too small
                }
                說明當前的較大數組的值太小了,需要捨棄一部分較小數組
                else if (i > iMin && A[i-1] > B[j]) {
                    iMax = i - 1; // i is too big
                }else { // i is perfect
                    int maxLeft = 0;
                    若較小數組被全部捨棄,直接到較大數組中尋找[(m+n)/2+1]
                    if (i == 0) { maxLeft = B[j-1]; }
                    若較大數組被全部捨棄,直接到較小數組中尋找[(m+n)/2+1]
                    else if (j == 0) { maxLeft = A[i-1]; }
                    否則比較兩個數哪個纔是真正的中位數
                    else { maxLeft = Math.max(A[i-1], B[j-1]); }
                    if ( (m + n) % 2 == 1 ) { return maxLeft; }
					下面幾乎相同,只不過取的是偶數的另一半,此時正好相反,哪個數小,哪個數纔是另一半
					因爲此時需要求的是大於中位數的數,所以越小靠近前面的中位數
                    int minRight = 0;
                    if (i == m) { minRight = B[j]; }
                    else if (j == n) { minRight = A[i]; }
                    else { minRight = Math.min(B[j], A[i]); }

                    return (maxLeft + minRight) / 2.0;
                }
            }
            return 0.0;
       }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章