[LeetCode]兩個排序數組的中位數(Median of Two Sorted Arrays)

題目描述

給定兩個大小爲 m 和 n 的有序數組 nums1 和 nums2 。
請找出這兩個有序數組的中位數。要求算法的時間複雜度爲 O(log (m+n)) 。java

示例 1:
nums1 = [1, 3]
nums2 = [2]
中位數是 2.0

示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
中位數是 (2 + 3)/2 = 2.5算法

解決方法

首先,讓咱們以一種很是規的方式看到'中位數'的概念。那是:數組

「 若是咱們將排序後的數組切割成等於兩半的等長,則平均數爲最大值(lower_half)和最小值(upper_half)的平均值,即緊靠切割的兩個數字 」。

例如,對於[2 3 5 7],咱們在3和5之間進行切割:code

[2 3 / 5 7]

那麼中位數=(3 + 5)/ 2。請注意,在本文中,我將使用'/'來表示切割和(數字/數字)來表示經過數字進行的切割。
對於[2 3 4 5 6],咱們將4切割:排序

[2 3(4/4)5 7]

因爲咱們把4分紅兩半,因此咱們說如今子單元的下部和上部都包含4。這個概念也致使了正確的答案:(4 + 4)/ 2 = 4。爲了方便起見,咱們使用L來表示切割的左邊對應的數字,R表示切割的右邊對應的數字。例如[2 3 5 7],咱們分別有L = 3和R = 5。咱們觀察到L和R的索引與數組N的長度有下列關係:索引

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。所以,中位數能夠表示爲ci

(L + R)/2 = (A[(N-1)/2] + A[N/2])/2

爲了準備好兩個數組的狀況,咱們在數字之間添加一些想象的「位置」(表示爲#),並將數字視爲「位置」。leetcode

[6 9 13 18] -> [# 6 # 9 # 13 # 18 #] (N = 4)
position index 0 1 2 3 4 5 6 7 8 (N_Position = 9)

[6 9 11 13 18]-> [# 6 # 9 # 11 # 13 # 18 #] (N = 5)
position index 0 1 2 3 4 5 6 7 8 9 10 (N_Position = 11)get

正如你所看到的,無論長度爲N,總有正好2 * N + 1的位置。所以,中間切割應該老是在第N個位置(基於0的位置)進行。在這種狀況下,因爲index(L)=(N-1)/2和index(R)=N/2,咱們能夠推斷it

index(L) = (CutPosition-1)/2, index(R) = (CutPosition)/2

如今對於兩個數組的狀況:

A1: [# 1 # 2 # 3 # 4 # 5 #] (N1 = 5, N1_positions = 11)
A2: [# 1 # 1 # 1 # 1 #] (N2 = 4, N2_positions = 9)

相似於單數組問題,咱們須要找到一個將兩個數組分紅兩半的切割

「左半部分的任何數字」<=「右半部分的任何數字」。

咱們也能夠提出如下意見:

  1. 共有2個N1 + 2個 N2 + 2個位置。所以,在切割的每一邊必須有N1 + N2的位置,而且直接在切割處有2個位置。
  2. 所以,當咱們在A2中的位置C2 = K處切割時,A1中的切割位置必須是C1 = N1 + N2-k。例如,若是C2 = 2,那麼咱們必須有C1 = 4 + 5 - C2 = 7。

    [# 1 # 2 # 3 # (4/4) # 5 #]
    [# 1 / 1 # 1 # 1 #]
  3. 切割完成後,咱們會有兩個L和兩個R。他們是

    L1 = A1[(C1-1)/2]; R1 = A1[C1/2];
    L2 = A2[(C2-1)/2]; R2 = A2[C2/2];

在上面的例子中,

L1 = A1[(7-1)/2] = A1[3] = 4; R1 = A1[7/2] = A1[3] = 4;
L2 = A2[(2-1)/2] = A2[0] = 1; R2 = A1[2/2] = A1[1] = 1;

如今咱們該如何決定這個切割是不是咱們想要的切割?由於L1,L2是左邊最大的數字,而R1,R2是右邊最小的數字,因此咱們只須要

L1 <= R1 && L1 <= R2 && L2 <= R1 && L2 <= R2

確保下半部分的任何數字<=上半部分的任何數字。事實上,由於L1 <= R1和L2 <= R2是天然保證的,由於A1和A2是分類的,咱們只須要確保:

L1 <= R2且L2 <= R1。

如今咱們可使用簡單的二分查找來找出結果。

若是咱們有L1> R2,這意味着在A1的左半部分有太多的大數字,那麼咱們必須將C1向左移動(即向右移動C2)。
若是L2> R1,那麼A2的左半部分有太多的大數字,咱們必須將C2移到左邊。
不然,這一切是正確的。
在咱們找到切割後,能夠得出結果爲(max(L1,L2)+ min(R1,R2))/ 2;

注意:

  • 因爲C1和C2能夠相互肯定,咱們能夠先移動其中一個,而後相應地計算另外一個。可是,首先將C2(較短數組上的一個)移動更爲實用。緣由是,在較短的數組上,全部位置均可能是中位數的切割位置,但在較長的數組上,左右位置過於靠後的位置對於合法切割根本不可能。例如,[1],[2 3 4 5 6 7 8]。很顯然,2和3之間的切割是不可能的,由於若是你用這種方式進行切割,較短的數組沒有那麼多元素來平衡[3 4 5 6 7 8]部分。所以,要將較長的數組用做第一次切割的基礎,必須執行範圍檢查。在較短的數組上執行操做會更容易,也無需進行任何檢查。
  • 惟一的邊緣狀況是當切割落在第0(第一個)或第2N (最後一個)位置時。例如,若是C2 = 2N2,則R2 = A2 [2 * N2 / 2] = A2 [N2],其超出數組的邊界。爲了解決這個問題,咱們能夠想象,A1和A2實際上有兩個額外的元素,INT_MAX在A [-1]和INT_MAX在A [N]。這些添加不會改變結果,但會使實現更容易:若是任何L落在數組的左邊界以外,則L = INT_MIN,而且若是有任何R落在右邊界以外,則R = INT_MAX。

我知道這不是很容易理解,但全部上述推理最終歸結爲如下簡潔的代碼:

public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int n1 = nums1.length;
        int n2 = nums2.length;
        if (n1 < n2)
            return findMedianSortedArrays(nums2, nums1); // 確保nums2爲短數組
        int lo = 0, hi = n2 * 2; //
        while (lo <= hi) {
            int c2 = (lo + hi) >> 1;
            int c1 = n1 + n2 - c2;
            double L1 = (c1 == 0) ? Integer.MIN_VALUE : nums1[(c1 - 1) / 2];
            double L2 = (c2 == 0) ? Integer.MIN_VALUE : nums2[(c2 - 1) / 2];
            double R1 = (c1 == n1 * 2) ? Integer.MAX_VALUE : nums1[c1 / 2];
            double R2 = (c2 == n2 * 2) ? Integer.MAX_VALUE : nums2[c2 / 2];

            if (L1 > R2)
                lo = c2 + 1; // 增大c2,減少c1,向右移動c2
            else if (L2 > R1)
                hi = c2 - 1; // 減少c2,增大c1,向左移動c2
            else
                return (Math.max(L1, L2) + Math.min(R1, R2)) / 2;
        }

        return -1;
    }

時間複雜度:O(log(min(M,N)))

算法解釋原文

原文連接:https://lierabbit.cn/2018/04/...

相關文章
相關標籤/搜索