給定兩個大小爲 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)
相似於單數組問題,咱們須要找到一個將兩個數組分紅兩半的切割
「左半部分的任何數字」<=「右半部分的任何數字」。
咱們也能夠提出如下意見:
所以,當咱們在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 #]
切割完成後,咱們會有兩個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;
注意:
我知道這不是很容易理解,但全部上述推理最終歸結爲如下簡潔的代碼:
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)))