LeetCode 4. Median of Two Sorted Arrays

LeonChen陳林峰發表於2018-08-14

[Chinese ver]

4. Median of Two Sorted Arrays

leonchen1024.com/2018/08/15/…

這裡有兩個有序陣列nums1和nums2,他們各自的大小為m和n. 找到這兩個陣列的中間值,總的時間複雜度應該為O(log (m+n)).

Example 1:
nums1 = [1, 3]
nums2 = [2]
 中間值是 2.0
複製程式碼
Example 2:
nums1 = [1, 2]
nums2 = [3, 4]
中間值是 (2 + 3)/2 = 2.5
複製程式碼

方法一:

首先嚐試了一種比較笨的方法

public class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        //需要幾個數值
        int needNum = 0;
        //數值位置
        int needIndex = 0;
        //中間值
        double median = 0;
        int big = 0;
        int stand = 0;
        //合成的陣列
        int[] insertnum ;

        int m = nums1.length;
        int n = nums2.length;

        if((m+n)%2==0){
            needNum = 2;
            needIndex = (m+n)/2-1;
        }else{
            needNum=1;
            needIndex = (m+n)/2;
        }
        insertnum = new int[m+n];
        //nums1的下標
        int j =0;
        //nums2的下標
        int k = 0;
        for(int i =0;i<m+n;i++){
            if(k==n){
                insertnum[i] = nums1[j];
                j++;
            }
            else if(j==m){
                insertnum[i] = nums2[k];
                k++;
            }else if(nums1[j]>nums2[k]){
                insertnum[i] = nums2[k];
                k++;
            }else{
                insertnum[i] = nums1[j];
                j++;
            }
        }
        if(needNum == 1){
            median = Double.valueOf(insertnum[needIndex]);
        }else{
            median = Double.valueOf(insertnum[needIndex]+insertnum[needIndex+1]) /2.0;
        }

        return median;

    }
}
複製程式碼

順便提一下,這個方法經常無法通過,應該是時間複雜度過高的原因吧。

效率

分析 這個方法的原理很簡單,首先將兩個陣列個數和是奇數和偶數的情況所需的取值個數以及中間值計算方法得出。然後將兩個陣列按照從小到大的順序合併成一個陣列,最後根據前面得到的中間數的計算方法得出中間值。 時間複雜度 : O(m+n) 。 空間複雜度 : O(m+n) .

這個方法可以在做一個優化,如下:


public class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int needNum = 0;
        int needIndex = 0;
        double median = 0;
        int big = 0;
        int stand = 0;
        int[] insertnum ;

        int m = nums1.length;
        int n = nums2.length;

        if((m+n)%2==0){
            needNum = 2;
            needIndex = (m+n)/2-1;
        }else{
            needNum=1;
            needIndex = (m+n)/2;
        }
        insertnum = new int[(m+n)/2+1];
        int j =0;
        int k = 0;
        for(int i =0;i<m+n;i++){

            if(k==n){
                insertnum[i] = nums1[j];
                j++;
            }
            else if(j==m){
                insertnum[i] = nums2[k];
                k++;
            }else if(nums1[j]>nums2[k]){
                insertnum[i] = nums2[k];
                k++;
            }else{
                insertnum[i] = nums1[j];
                j++;
            }

             if (i >= needIndex ){
                if (needNum == 1){
                    median = Double.valueOf(insertnum[needIndex]);
                    return median;
                }else{
                     if (i == needIndex+1){
                         median = Double.valueOf(insertnum[needIndex]+insertnum[needIndex+1]) /2.0;
                         return median;
                     }
                }
            }

        }
    return median;
    }
}

複製程式碼

分析 這個方法優化的地方就是通過事先得到中間值的位置,在得到該位置的數值之後就不繼續往下走了。

效率

方法二:

public class Solution {
       public double findMedianSortedArrays(int[] nums1, int[] nums2) {
           int N1 = nums1.length;
           int N2 = nums2.length;
           if (N1 < N2)
               return findMedianSortedArrays(nums2, nums1);    // Make sure A2 is the shorter one.

           if (N2 == 0)
               return ((double) nums1[(N1 - 1) / 2] + (double) nums1[N1 / 2]) / 2;  // If A2 is empty

           int lo = 0, hi = N2 * 2;
           while (lo <= hi) {
               int mid2 = (lo + hi) / 2;   // Try Cut 2
               int mid1 = N1 + N2 - mid2;  // Calculate Cut 1 accordingly

               double L1 = (mid1 == 0) ? Integer.MIN_VALUE : nums1[(mid1 - 1) / 2];    // Get L1, R1, L2, R2 respectively
               double L2 = (mid2 == 0) ? Integer.MIN_VALUE : nums2[(mid2 - 1) / 2];
               double R1 = (mid1 == N1 * 2) ? Integer.MAX_VALUE : nums1[(mid1) / 2];
               double R2 = (mid2 == N2 * 2) ? Integer.MAX_VALUE : nums2[(mid2) / 2];

               if (L1 > R2)
                   lo = mid2 + 1;        // A1's lower half is too big; need to move C1 left (C2 right)
               else if (L2 > R1)
                   hi = mid2 - 1;    // A2's lower half too big; need to move C2 left.
               else
                   return ((L1 > L2 ? L1 : L2) + (R1 > R2 ? R2 : R1)) / 2;    // Otherwise, that's the right cut.
           }
           return -1;
       }
   }
複製程式碼

效率

分析

這個問題比較困難,很多方法都是把他分成兩種情況,奇數個和偶數個。實際上這樣有點複雜,我們可以把這兩個情況合併成一種情況。

首先,我們來重新定義一下'median'的含義:

“如果我們把一個有序的array分成兩個等長的部分,那麼median就是比較小的那一部分的最大值和比較大的那一部分的最小值的平均值。即劃分線兩邊的數。”

比如[1,2],我們的分割線就應該在1,2之間,[1 | 2],結果是(1 + 2)/2.0 = 1.5。 同理,如果是兩個有序的陣列,那麼我們只需要保證兩個陣列劃分線右側的數都大於左側的數,並且兩個陣列左側數字個數的和等於右側數字個數的和。

我們使用‘|’來代表分割線,(n|n)來表示分割線正好在某個數字n上。 比如[1 2 3],得到的分割後的陣列像這樣[1 (2|2) 3],z中間值就是(2+2)/2.0 = 2

我們使用L代表分割線左邊的數,R代表分割線右邊的數。

可以觀察到左邊的數和右邊的數有這樣的規律。

N Index of L / R 1 0 / 0 2 0 / 1 3 1 / 1 4 1 / 2 5 2 / 2 6 2 / 3 7 3 / 3 8 3 / 4

可以得到 L = (N-1)/2, R = N/2. 中間值為(L + R)/2 = (A[(N-1)/2] + A[N/2])/2

為了更方便的計算兩個陣列的情況,我們新增一些空位在數字之間。用'#'代表,用'position'來表示增加空格後的位置資訊。

[1 2] -> [# 1 # 2 #] (N=2) position 0 1 2 3 4 (N_Position = 5)

[3 4] -> [# 3 # 4 #] (N=2) position 0 1 2 3 4 (N_Position = 5)

可以看出N_Position = 2N+1.所以分割線是在N,index(L) = (CutPosition-1)/2, index(R) = (CutPosition)/2.

當我們分割這兩個陣列的時候要注意,總共有2N1+2N2+2的位置,所以,分割線的每一邊都要有N1+N2個位置,剩下的2個就是切線的位置,所以當我們A2分割線在 C2 = K , 那麼A1的分割線就必須要在C1 = N1 + N2 - k. 比如, C2 = 2, 那麼 C1 = 2 + 2 - C2 = 2.

[# 1 | 2 #] [# 3 | 4 #] 現在我們有了兩個L和兩個R。 L1 = A1[(C1-1)/2]; R1 = A1[C1/2]; L2 = A2[(C2-1)/2]; R2 = A2[C2/2]; 即 L1 = A1[(2-1)/2] = A1[0] = 1; R1 = A1[2/2] = A1[1] = 2; L2 = A2[(2-1)/2] = A2[0] = 3; R2 = A1[2/2] = A1[1] = 4;

那麼我們要怎麼判斷這個分割線是不是符合規則的呢?我們只要左邊的數字都小於右邊的數字。首先這兩個陣列都是有序的,所以L1,L2是左半邊陣列裡最大的數,R1,R2是右半邊陣列裡最小的數,L1<=R1.L2<=R2. 所以我們只要判斷L1<=R2,L2<=R1就可以了。

現在我們用簡單的二分法查詢就可以得到答案了。

如果L1>R2,代表A1左邊有過多較大的數字,我們必須把C1往左移,同時需要把C2往右移。 如果L2>R1,代表A2左邊有過多較大的數字,我們必須把C2往左移,同時需要把C1往右移。 反之則相反。

當我們找到正確的分割線位置的時候,中間值 = (max(L1, L2) + min(R1, R2)) / 2。

1.因為C1和C2可以根據彼此的值互相推斷,我們可以使用較短的陣列(設為A2)並且只移動C2,然後計算出C1。這樣我們的時間複雜度就是O(log(min(N1, N2)))

2.只有在極限的條件下分割線會在0,或者2N的index。比如,C2=2N2,R2 = A2[2*N2/2] = A2[N2].這種情況下他有一邊是沒有數值的,我們可以把它當成是極大或者極小,L = INT_MIN ,R = INT_MAX.

時間複雜度 : O(log(min(m,n))) 。m,n是兩個陣列的長度。 空間複雜度 : O(1) .

方法三:

public class Solution {
  public double findMedianSortedArrays(int A[], int B[]) {
       int n = A.length;
       int m = B.length;
       // the following call is to make sure len(A) <= len(B).
       // yes, it calls itself, but at most once, shouldn't be
       // consider a recursive solution
       if (n > m)
           return findMedianSortedArrays(B, A);

       // now, do binary search
       int k = (n + m - 1) / 2;
       int l = 0, r = Math.min(k, n); // r is n, NOT n-1, this is important!!
       while (l < r) {
           int midA = (l + r) / 2;
           int midB = k - midA;
           if (A[midA] < B[midB])
               l = midA + 1;
           else
               r = midA;
       }

       // after binary search, we almost get the median because it must be between
       // these 4 numbers: A[l-1], A[l], B[k-l], and B[k-l+1]

       // if (n+m) is odd, the median is the larger one between A[l-1] and B[k-l].
       // and there are some corner cases we need to take care of.
       int a = Math.max(l > 0 ? A[l - 1] : Integer.MIN_VALUE, k - l >= 0 ? B[k - l] : Integer.MIN_VALUE);
       if (((n + m) & 1) == 1)
           return (double) a;

       // if (n+m) is even, the median can be calculated by
       //      median = (max(A[l-1], B[k-l]) + min(A[l], B[k-l+1]) / 2.0
       // also, there are some corner cases to take care of.
       int b = Math.min(l < n ? A[l] : Integer.MAX_VALUE, k - l + 1 < m ? B[k - l + 1] : Integer.MAX_VALUE);
       return (a + b) / 2.0;
   }
}
複製程式碼

效率

分析 這個方法的原理和上一個方法是大致相同的。 時間複雜度 : O(log(min(m,n))) 。m,n是兩個陣列的長度。 空間複雜度 : O(1) .

方法四:

public class Solution {
  public double findMedianSortedArrays(int[] A, int[] B) {
              int m = A.length, n = B.length;
              int l = (m + n + 1) / 2;//position of l element (not the index)
              int r = (m + n + 2) / 2;//position of r element (not the index)
              return (getkth(A, 0, B, 0, l) + getkth(A, 0, B, 0, r)) / 2.0;
          }

  public double getkth(int[] A, int aStart, int[] B, int bStart, int k) {
          //when the start position is bigger than A.length -1 ,means the median doesn't in the A.
          if (aStart > A.length - 1) return B[bStart + k - 1];
          if (bStart > B.length - 1) return A[aStart + k - 1];
          if (k == 1) return Math.min(A[aStart], B[bStart]);

          int aMid = Integer.MAX_VALUE, bMid = Integer.MAX_VALUE;
          if (aStart + k/2 - 1 < A.length) aMid = A[aStart + k/2 - 1];
          if (bStart + k/2 - 1 < B.length) bMid = B[bStart + k/2 - 1];

          if (aMid < bMid)
              return getkth(A, aStart + k/2, B, bStart, k - k/2);// Check: aRight + bLeft
          else
              return getkth(A, aStart, B, bStart + k/2, k - k/2);// Check: bRight + aLeft
  }

}
複製程式碼

效率

分析 這個方法的原理是這樣的,從兩個陣列中間值開始通過遞迴對比,每次排除一半的選項。如果A的中間值小於B的中間值則保留a的右側數字和b的左側數字。反之相反。然後得到l和r位置的數字,再相加除以2即可。需要注意的是這裡的l和r不是index,不是從零開始的。如果兩個陣列的長度的和是奇數的話l和r是相同的,偶數則不同。 時間複雜度 : O(log(m + n)) 空間複雜度 : O(1)

如果你有更好的辦法或者對我這裡的描述有其他看法,請聯絡我。謝謝

About Me

我的部落格 leonchen1024.com

我的 GitHub github.com/LeonChen102…

微信公眾號

wechat

相關文章