LeetCode - 4. Median of Two Sorted Arrays : 逆推法 O(log(min(m,n))))

4. Median of Two Sorted Arrays

There are two sorted arrays nums1 and nums2 of size m and n respectively.

Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).

Example 1:

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

The median is 2.0

Example 2:

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

The median is (2 + 3)/2 = 2.5

題意理解:

給定任意兩個已經排好序的數組, 要你求這兩個數組的中位數. 並且時間複雜度不能大於O(log (m+n) )


思路歷程:

1. 合併成一個數組, 再求其中位數. 於是想到了合併的算法(按思考的時間先後)

  1. 插入排序. 粗略一算, 時間複雜度爲O(m*n).
  2. 二分插入. 粗略一算, 複雜度爲 O(nlogm), (假設n<m)

可以看出要達到log(m+n)這個等級, 合併這條路走不通.


2. 逆推法. 假設成功合併了nums1(簡稱A,長度爲n) 和nums2(簡稱B,長度爲m), 得到一個數組C .通過觀察C和它的中位數median可初步得到以下結論

  1. C.length = m + n
  2. 對於任意的 c1 ∈ C1, c2 ∈ C2 均有 c1 <= c2, 即 MAX(C1) <= MIN(C2)
  3. median可把M分成左半部分(C1), 和右半部分(C2).  並且C1.length == C2.length (或 C1.length == C2.length + 1, 將median放在C1)
中位數爲 median = (MAX(C1) + MIN(C2)) /2  (m+n 爲偶數) 或 MAX(C1)  (m+n爲奇數). (將median放在C1)
則任務轉換爲, 已知 A,B , 求滿足上述條件的 MAX(C1),MIN(C2) 

要滿足條件1: 很容易滿足....

要滿足條件2:  由MAX(C1) <= MIN(C2) 可知
  1. MAX(C1中 來自A的元素) <= MIN(C2中 來自A的元素) (顯然成立)
  2. MAX(C1中 來自A的元素) <= MIN(C2中 來自B的元素)
  3. MAX(C1中 來自B的元素) <= MIN(C2中 來自B的元素) (顯然成立)
  4. MAX(C1中 來自B的元素) <= MIN(C2中 來自A的元素) 
要滿足條件3: C1.length == C2.length (或 C1.length  == 1+ C2.length , 將median放在C2) , 設C1中 來自A的個數爲x, 來自B的個數爲y, 要滿足
  1. C1.length = x + y , C2.length = m + n - x - y  (或 m + n - x - y + 1)
即滿足  y = (m + n + 1) /2  - x  (利用除2向下取整,合併兩種情況)
what?這個式子的意思就是 C1中 A的個數和B的個數滿足線性關係, 這意味着啥? 
這意味着 只要找到 x (C1中 A元素的數量) , 使得A,B 滿足 條件2即可. 此話怎講?
一個(x,y)的劃分如圖:

即滿足:
  • A[x-1] <= B[y]
  • B[y-1] <= A[x]
臨界值有 x =0, x = n , y = 0 , y = m(稍後討論)
  • 將x 從 0 到 n 進行順序遍歷, 這樣複雜度爲O(n)
  • 從0到n 對x 進行二分查找, 這樣複雜度爲O(logn)
由此看來, 問題是可解決並且符合題目要求的.

得到初步得到算法:
var left = 0;
    var right = A.length;
    while(left <= right)
    {
        var x = (left + right) / 2;
        var y = (A.length + B.length + 1) / 2 - x;
        if(A[x-1] > B[y])
        {
            //說明A[x-1]太大了.
            //如果 x增大, 則y會減少, 這樣A[x-1]會更加比B[y]大.
            //所以 x應該減少.
            right = x;
        }
        else if(B[y-1] > A[x])
        {
            //說明A[x]太小了
            //如果 x減小,則 y會增大, 這樣B[y-1]會比A[x]更大.
            //所以 x應該增大.
            left = x;
        }
        else {
            //說明x剛好滿足條件2.
            var MAXA = A[x-1];
            var MINB = B[y];
            if((A.length + B.length )%2 == 0)
            {
                return (MAXA + MINB) / 2;
            }
            else{
                return MAXA*1.0;
            }
        }
    }
下面討論 x=0 , x=n, y=0, y=m的情況
這四種情況, 對應的是
  • C1中 A爲空集 (導致A[x-1] 不存在)
  • C2中 A爲空集 (導致A[x]不存在)
  • C1中 B爲空集 (導致B[y-1]不存在)
  • C2中 B爲空集 (導致B[y]不存在)
若出現這四種, 將其視爲滿足條件
則有:
<span style="white-space:pre"></span>if (x !== 0 && y !== B.length && A[x - 1] > B[y]) {
    //說明A[x-1]太大了.
    //如果 x增大, 則y會減少, 這樣A[x-1]會更加比B[y]大.
    //所以 x應該減少.
    right = x - 1;
}
else if (y !== 0 && x !== A.length && B[y - 1] > A[x]) {
    //說明A[x]太小了
    //如果 x減小,則 y會增大, 這樣B[y-1]會比A[x]更大.
    //所以 x應該增大.
    left = x + 1;
}
在獲取最後結果的時候要也要判斷:
//說明x剛好滿足條件2.
            if (x === 0) {
                var MAX_C1 = B[y - 1];
            }
            else if (y === 0) {
                var MAX_C1 = A[x - 1];
            }
            else {
                var MAX_C1 = A[x - 1] > B[y - 1] ? A[x - 1] : B[y - 1];
            }
            
            if (x === A.length) {
                var MIN_C2 = B[y];
            }
            else if (y === B.length) {
                var MIN_C2 = A[x];
            }
            else {
                var MIN_C2 = A[x] > B[y] ? B[y] : A[x];
            }

            if ((A.length + B.length) % 2 == 0) {
                return (MAX_C1 + MIN_C2) / 2;
            }
            else {
                return MAX_C1 * 1.0;
            }

以下爲完整代碼:
/**
 * @param {number[]} A
 * @param {number[]} B
 * @return {number}
 */
var findMedianSortedArrays = function (A, B) {
    //確保 A.length < B.length 實現 log(MIN(m,n))
    if (A.length > B.length) {
        var temp = B;
        B = A;
        A = temp;
    }
    var left = 0;
    var right = A.length;
    while (left <= right) {
        var x = parseInt((left + right) / 2);
        var y = parseInt((A.length + B.length + 1) / 2) - x;
        if (x !== 0 && y !== B.length && A[x - 1] > B[y]) {
            //說明A[x-1]太大了.
            //如果 x增大, 則y會減少, 這樣A[x-1]會更加比B[y]大.
            //所以 x應該減少.
            right = x - 1;
        }
        else if (y !== 0 && x !== A.length && B[y - 1] > A[x]) {
            //說明A[x]太小了
            //如果 x減小,則 y會增大, 這樣B[y-1]會比A[x]更大.
            //所以 x應該增大.
            left = x + 1;
        }
        else {
            //說明x剛好滿足條件2.
            if (x === 0) {
                var MAX_C1 = B[y - 1];
            }
            else if (y === 0) {
                var MAX_C1 = A[x - 1];
            }
            else {
                var MAX_C1 = A[x - 1] > B[y - 1] ? A[x - 1] : B[y - 1];
            }
            
            if (x === A.length) {
                var MIN_C2 = B[y];
            }
            else if (y === B.length) {
                var MIN_C2 = A[x];
            }
            else {
                var MIN_C2 = A[x] > B[y] ? B[y] : A[x];
            }

            if ((A.length + B.length) % 2 == 0) {
                return (MAX_C1 + MIN_C2) / 2;
            }
            else {
                return MAX_C1 * 1.0;
            }
        }
    }
};

發佈了63 篇原創文章 · 獲贊 55 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章