给定两个大小为 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)))