(LeetCode刷題)Day04 尋找兩個有序數組的中位數

Median of Two Sorted Arrays

題目描述

在這裏插入圖片描述
在這裏插入圖片描述

解法: 遞歸法

爲了解決這個問題,我們需要理解 “中位數的作用是什麼”。在統計中,中位數被用來:

將一個集合劃分爲兩個長度相等的子集,其中一個子集中的元素總是大於另一個子集中的元素。

如果理解了中位數的劃分作用,我們就很接近答案了。

首當其衝的來講,讓我們在任一位置 iiA\text{A} 劃分成兩個部分:

          left_A             |        right_A
    A[0], A[1], ..., A[i-1]  |  A[i], A[i+1], ..., A[m-1]

由於 A\text{A} 中有 mm 個元素, 所以我們有 m+1m+1 種劃分的方法(i=0mi = 0 \sim m)。

因此我們可以得知:

len(leftA)=i,len(rightA)=mi.len(left_A)=i,len(right_A)=m−i.
i=0i = 0的時候,leftAleft_A爲空集,而當i=mi = m的時候,rightAright_A爲空集。

採用同樣的方式,我們在任一位置 jjB\text{B} 劃分成兩個部分:

          left_B             |        right_B
    B[0], B[1], ..., B[j-1]  |  B[j], B[j+1], ..., B[n-1]

left_A\text{left\_A}left_B\text{left\_B} 放入一個集合,並將 right_A\text{right\_A}right_B\text{right\_B} 放入另一個集合。 再把這兩個新的集合分別命名爲 left_part\text{left\_part}right_part\text{right\_part}

          left_part          |        right_part
    A[0], A[1], ..., A[i-1]  |  A[i], A[i+1], ..., A[m-1]
    B[0], B[1], ..., B[j-1]  |  B[j], B[j+1], ..., B[n-1]

那麼當我們可以確認一個情況的時候:

  1. len(left_part)=len(right_part)len(\text{left\_part})=len(\text{right\_part})
  2. max(left_part)min(right_part)max(\text{left\_part}) ≤ min(\text{right\_part})

那麼,我們已經將 {A,B}\{\text{A}, \text{B}\} 中的所有元素劃分爲相同長度的兩個部分,且其中一部分中的元素總是大於另一部分中的元素。那麼:

median=max(left_part)+min(right_part)2median = \frac{max(\text{left\_part}) + min(\text{right\_part})}2

要確保這兩個條件,我們只需要保證:

  1. i+j=mi+ni+j = m - i + n或者 mi+nj+1m - i + n - j + 1

  2. Bj1A[i]B|j - 1| ≤ A[i]以及A[i1]B[j]A[i−1]≤B[j]

  3. 爲了簡化分析,我假設 A[i1],B[j1],A[i],B[j]\text{A}[i-1], \text{B}[j-1], \text{A}[i], \text{B}[j]總是存在,哪怕出現 i=0i=0i=mi=mj=0j=0,或是 j=nj=n 這樣的臨界條件。我將在最後討論如何處理這些臨界值。

  4. 爲什麼 n>m?n > m?由於$0 \leq i \leq m0≤i≤m $ 且 j=m+n+12ij = \frac{m + n + 1}2 - i,我必須確保 jj 不是負數。如果 n<mn < m 那麼 jj 將可能是負數,而這會造成錯誤的答案。

因此我們接下來要處理的事情就是:

[0m][0,m] 中搜索並找到目標對象 ii,以達到:
B[j1]A[i]B[j−1]≤A[i]A[i1]B[j]\text{A}[i-1] \leq \text{B}[j], 其中 j=m+n+12ij = \frac{m + n + 1}{2} - i

接着,我們可以按照以下步驟來進行二叉樹搜索:

  1. imin=0\text{imin} = 0imax=m\text{imax} = m, 然後開始在 [imin,imax][\text{imin}, \text{imax}] 中進行搜索。

  2. i=imin+imax2i = \frac{\text{imin} + \text{imax}}{2}

  3. 現在我們有 len(left_part)=len(right_part)\text{len}(\text{left}\_\text{part})=\text{len}(\text{right}\_\text{part})。 而且我們只會遇到三種情況:

    • B[j1]A[i]A[i1]B[j]B[j−1]≤A[i] 且 \text{A}[i-1] \leq \text{B}[j]:這意味着我們找到了目標對象 ii,所以可以停止搜索。
    • B[j1]>A[i]B[j−1]>A[i]:這意味着 A[i]\text{A}[i] 太小,我們必須調整 ii 以使 B[j1]A[i]\text{B}[j-1] \leq \text{A}[i]
      我們可以增大 ii 嗎?
      是的,因爲當 ii 被增大的時候,jj 就會被減小。
      因此 B[j1]\text{B}[j-1] 會減小,而 A[i]\text{A}[i] 會增大,那麼 \text{B}[j-1] \leq \text{A}[i]B[j−1]≤A[i] 就可能被滿足。
      我們可以減小 ii 嗎?
      不行,因爲當 ii 被減小的時候,jj 就會被增大。
      因此 B[j1]\text{B}[j-1] 會增大,而 A[i]\text{A}[i] 會減小,那麼 B[j1]A[i]\text{B}[j-1] \leq \text{A}[i] 就可能不滿足。
      所以我們必須增大 ii。也就是說,我們必須將搜索範圍調整爲 [i+1,imax][i+1, \text{imax}]。因此,設 imin=i+1\text{imin} = i+1,並轉到步驟 2。
    • A[i1]>B[j]A[i−1]>B[j]
      這意味着 A[i1]\text{A}[i-1] 太大,我們必須減小 ii 以使 A[i1]B[j]\text{A}[i-1]\leq \text{B}[j]
      也就是說,我們必須將搜索範圍調整爲 [imin,i1][\text{imin}, i-1]
      因此,設 imax=i1\text{imax} = i-1,並轉到步驟 2。

當找到目標對象 ii 時,中位數爲:

  • max(A[i1],B[j1])max(A[i−1],B[j−1]), 當 m+nm + n爲奇數時

  • max(A[i1],B[j1])+min(A[i],B[j])2,\frac{\max(\text{A}[i-1], \text{B}[j-1]) + \min(\text{A}[i], \text{B}[j])}{2}, , 當 m+nm + n 爲偶數時

現在,讓我們來考慮這些臨界值 i=0,i=m,j=0,j=ni=0,i=m,j=0,j=n,此時$ \text{A}[i-1],\text{B}[j-1],\text{A}[i],\text{B}[j]$ 可能不存在。其實這種情況比你想象的要容易得多。

我們需要做的是確保 max(left_part)min(right_part)\text{max}(\text{left}\_\text{part}) \leq \text{min}(\text{right}\_\text{part})。 因此,如果 ii 和 jj 不是臨界值(這意味着 A[i1],B[j1],A[i],B[j]\text{A}[i-1], \text{B}[j-1],\text{A}[i],\text{B}[j]全部存在), 那麼我們必須同時檢查 B[j1]A[i]\text{B}[j-1] \leq \text{A}[i] 以及 A[i1]B[j]\text{A}[i-1] \leq \text{B}[j] 是否成立。

但是如果A[i1],B[j1],A[i],B[j]\text{A}[i-1],\text{B}[j-1],\text{A}[i],\text{B}[j]中部分不存在,那麼我們只需要檢查這兩個條件中的一個(或不需要檢查)。
舉個例子,如果 i=0i = 0,那麼 A[i1]\text{A}[i-1] 不存在,我們就不需要檢查 A[i1]B[j]\text{A}[i-1] \leq \text{B}[j] 是否成立。
所以,我們需要做的是:

在 [0,m][0,m] 中搜索並找到目標對象 ii,以使:

  • (j=0ori=morB[j1]A[i])(j = 0 or i = m or \text{B}[j-1] \leq \text{A}[i]) 或是
  • (i=0orj=norA[i1]B[j])(i = 0 or j = n or \text{A}[i-1] \leq \text{B}[j]), 其中j=m+n+12ij = \frac{m + n + 1}{2} - i

在循環搜索中,我們只會遇到三種情況:

  1. (j=0ori=morB[j1]A[i])(j=0 or i = m or \text{B}[j-1] \leq \text{A}[i])或是 (i=0orj=norA[i1]B[j])(i = 0 or j = n or \text{A}[i-1] \leq \text{B}[j]),這意味着 ii 是完美的,我們可以停止搜索。
  2. j>0andi<mandB[j1]>A[i]j>0 and i < m and \text{B}[j - 1] > \text{A}[i] 這意味着 ii 太小,我們必須增大它。
  3. i>0andj<nandA[i1]>B[j]i>0 and j < n and \text{A}[i - 1] > \text{B}[j]這意味着 ii 太大,我們必須減小它。

i<m    j>0i < m \implies j > 0 以及 i>0    j<ni > 0 \implies j < n 始終成立,這是因爲:

mn,i<m    j=m+n+12i>m+n+12m2m+12m0m≤n, i<m \implies j = \frac{m+n+1}{2} - i > \frac{m+n+1}{2} - m ≥ \frac{2m+1}{2} - m ≥ 0
mn,i>0    j=m+n+12i<m+n+122n+12nm≤n, i>0 \implies j = \frac{m+n+1}{2} - i < \frac{m+n+1}{2} ≤ \frac{2n+1}{2} ≤ n

所以,在情況 2 和 3中,我們不需要檢查 j>0j > 0 或是 j<nj < n 是否成立。

複雜度分析

  • 時間複雜度:O(log(min(m,n)))O\big(\log\big(\text{min}(m,n)\big)\big)
    首先,查找的區間是 [0,m][0, m]
    而該區間的長度在每次循環之後都會減少爲原來的一半。
    所以,我們只需要執行 log(m)\log(m) 次循環。由於我們在每次循環中進行常量次數的操作,所以時間複雜度爲 O(log(m))O\big(\log(m)\big)
    由於 mnm \leq n,所以時間複雜度是 O(log(min(m,n)))O\big(\log\big(\text{min}(m,n)\big)\big)
  • 空間複雜度:O(1)O(1)
    我們只需要恆定的內存來存儲 99 個局部變量, 所以空間複雜度爲 O(1)O(1)

C++代碼

class Solution
{
public:
    double findMedianSortedArrays(vector<int> &nums1, vector<int> &nums2)
    {
        int nums1Size = int(nums1.size());
        int nums2Size = int(nums2.size());

        //確保數組1是較短的數組
        if (nums1Size > nums2Size)
        {
            return findMedianSortedArrays(nums2, nums1);
        }

        // Ci 爲第i個數組的割,比如C1爲2時表示第1個數組只有2個元素。lMaxi爲第i個數組割後的左元素。rMini爲第i個數組割後的右元素。
        int lMax1, lMax2, rMin1, rMin2, c1, c2, lo = 0, hi = 2 * nums1Size; //我們目前是虛擬加了'#'所以數組1是2*n長度

        while (lo <= hi)
        { //二分法
            c1 = (lo + hi) / 2;
            c2 = nums1Size + nums2Size - c1;

            lMax1 = (c1 == 0) ? INT_MIN : nums1[(c1 - 1) / 2];
            rMin1 = (c1 == 2 * nums1Size) ? INT_MAX : nums1[c1 / 2];
            lMax2 = (c2 == 0) ? INT_MIN : nums2[(c2 - 1) / 2];
            rMin2 = (c2 == 2 * nums2Size) ? INT_MAX : nums2[c2 / 2];

            if (lMax1 > rMin2)
            {
                hi = c1 - 1;
            }
            else if (lMax2 > rMin1)
            {
                lo = c1 + 1;
            }
            else
            {
                break;
            }
        }
        return (max(lMax1, lMax2) + min(rMin1, rMin2)) / 2.0;
    }
};

Golang代碼

// Solution by Panda.

// 生成一個新的數組,然後判斷長度奇偶數,取中間值。
func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
    nums := combine(nums1, nums2)
    return medianOf(nums)
}

func combine(mis, njs []int) []int {
    lenMis, i := len(mis), 0
    lenNjs, j := len(njs), 0
    res := make([]int, lenMis+lenNjs)
    
    for k := 0; k < lenMis+lenNjs; k++ {
        if i == lenMis || 
        (i < lenMis && j < lenNjs && mis[i] > njs[j]) {
            res[k] = njs[j]
            j++
            continue
        }
        
        if j == lenNjs ||
        (i < lenMis && j < lenNjs && mis[i] <= njs[j]) {
            res[k] = mis[i]
            i++
        }
    }
    
    return res
}

func medianOf(nums []int) float64 {
    l := len(nums)
    
    if l == 0 {
        panic("切片長度爲0, 無法求解中位數.")
    }
    
    if l%2 == 0 {
        return float64(nums[l/2]+nums[l/2-1]) / 2.0
    }
    
    return float64(nums[l/2])
}

Java代碼

class Solution {
    public double findMedianSortedArrays(int[] A, int[] B) {
        int m = A.length;
        int n = B.length;
        if (m > n) { // to ensure m<=n
            int[] temp = A; A = B; B = temp;
            int tmp = m; m = n; n = tmp;
        }
        int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2;
        while (iMin <= iMax) {
            int i = (iMin + iMax) / 2;
            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;
                if (i == 0) { maxLeft = B[j-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;
    }
}

在這裏插入圖片描述
成長,就是一個不動聲色的過程,一個人熬過一些苦,才能無所不能。 ​​​​

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