尋找兩個有序數組中的中位數

遇到一個比較複雜的算法題,記錄一下,內容以下。算法


1.問題

假設有兩個有序數組nums1和nums2,它們的長度分別爲n和m。請找出這兩個有序數組組成的序列中的中位數,而且總體的時間複雜度不大於log(m+n)。你能夠假設這兩個數組都不爲空。數組

例子

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

The median is 2.0
複製代碼

2.分析

解決這個問題能夠採用遞歸的方法,而尋找中位數的問題,能夠理解爲:將一個集合劃分爲兩個等長的子集,其中一個子集的任意元素值永遠小於等於另外一個子集的任意元素值。
首先,咱們能夠經過隨機位置i將數組A劃分爲兩部分(能夠將i理解爲數組元素之間的間隔位置,間隔i左邊有i個元素,右邊有m-i個元素):bash

left_A             |        right_A
A[0], A[1], ..., A[i-1]  |  A[i], A[i+1], ..., A[m-1]
複製代碼

由於A有m個元素,所以共有m+1種切分方式。咱們能夠得出:len(left_A)=i,len(right_A)=m-i。當i=0時(第一個位置爲0),left_A爲空,當i=m時,right_A爲空。(注:len(left_A)表明left_A部分的元素個數,其餘的同理)
同理,咱們也能夠經過隨機位置j將數組B劃分爲兩部分:ui

left_B             |        right_B
B[0], B[1], ..., B[j-1]  |  B[j], B[j+1], ..., B[n-1]
複製代碼

所以,咱們能夠將left_A和left_B合併爲一個集合,將right_A和right_B合併爲一個集合,分別命名爲left_part和right_part:spa

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]
複製代碼

若是咱們可以確保(注:max(left_part)表明left_part集合的最大值,min(rigth_part)表明right_part集合的最小值):code

1.len(left_part)=len(right_part);
2.max(left_part)<=min(rigth_part)
複製代碼

那麼咱們就將{A,B}集合中的元素劃分爲具備相同長度的兩個子集了,其中一個子集的元素永遠大於等於另一個子集的元素。那麼 median(中位數)=(max(left_part)+min(right_part))/2。(以上的討論基於m,n都是偶數,但不失通常性)cdn

所以爲了保證以上的兩個條件成立,咱們能夠保證(當m+n爲奇數時,左邊部分會比右邊部分多一個元素): blog

在這裏,咱們假設:
1.爲了方便討論,咱們假設當i=0, i=m,j=0, j=n 時,A[i−1], B[j−1], A[i], B[j]的值都存在。咱們在最後再討論這些邊界值的狀況。
2.假設n≥m,由於咱們必須確保j不爲負數,當0≤i≤m,j=(m+n+1)/2-i。

所以咱們須要作的就是,遍歷i(i屬於[0,m]),找到一個目標i使得: 遞歸


咱們能夠經過如下的步驟進行二分查找:
1.令imin=0,imax=m,從[imin,imax]開始查找。
2.令i=(imin+imax)/2, j=(m+n+1)/2-i。
3.如今咱們已經使得len(left_part)=len(right_part)。咱們可能會遇到兩種狀況:

  • B[j−1]≤A[i] 和A[i−1]≤B[j]這意味着咱們找到了目標i所以查詢結束。
  • B[j−1]>A[i],這意味着A[i]過小了,所以咱們須要增大i,使得B[j−1]≤A[i]。由於i增大了j就會減少,A[i]就會增大,B[j−1]就會減少。所以,咱們將查詢範圍調整爲[i+1,imax],返回第2步。
  • A[i−1]>B[j]這意味着A[i−1]太大了,所以咱們須要減少i,因此咱們將查詢範圍調整爲[imin,i-1],返回第2步。

當咱們得到目標i時,咱們能夠獲得中位數:
1.當m+n時奇數時,中位數爲max(A[i−1],B[j−1])。
2.當m+n時偶數時,中位數爲(max(A[i−1],B[j−1])+min(A[i],B[j]))/2內存

最後,咱們來考慮一下邊緣值。當i=0, i=m, j=0, j=n時,A[i−1], B[j−1], A[i], B[j]不存在。當i,j不是邊緣值時,咱們須要判斷B[j−1]≤A[i] 和A[i−1]≤B[j]這兩個條件,當i, j是邊緣值時,這兩個條件就不須要都進行判斷了,好比,當i=0時,A[i-1]不存在,所以咱們就不須要判斷A[i−1]≤B[j]這個條件了。因此咱們須要作的是,遍歷i(i屬於[0,m]),找到目標值i使得:

好比j=0 則 A[i−1]≤B[j]必然成立;i=m 則 A[i−1]≤B[j] 必然成立。

所以,總得來講,當咱們在循環時,只會碰到三種狀況:
第一種狀況:找到目標 i,當:

第二種狀況:B[j−1]>A[i],這意味着A[i]過小了,所以咱們須要增大i;
第三種狀況:A[i−1]>B[j]這意味着A[i−1]太大了,所以咱們須要減少i。

3.具體代碼

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;//i
        while (iMin <= iMax) {//每次循環長度都變爲原來的1/2
            int i = (iMin + iMax) / 2;
            int j = halfLen - i;
            //調整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;
    }
}
複製代碼

4.複雜度分析

1.時間複雜度:O(log(min(m,n)))。在最開始查找範圍是[0,m],每次查詢時查找的範圍都會減小爲原來的一半,所以咱們須要log(m)次循環,而每次循環的時間都是常數,所以時間複雜度爲:O(log(m))。由於m≤n,因此時間複雜度爲O(log(min(m,n)))。
2.空間複雜度:O(1)。咱們只須要固定的內存去存儲9個本地變量,所以時間複雜度爲O(1)。

5.其餘:

咱們能夠將這兩個有序數組進行歸併,找到中位數,但這種方法的時間複雜度爲O(n+m)。

相關文章
相關標籤/搜索