给定两个大小为 m 和 n 的有序数组 nums1
和 nums2
。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1
和 nums2
不会同时为空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
递归、分治法
不想抄解题过程了,感觉很简单,贴一份最后的答案:
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;
while (iMin <= iMax) {
int i = (iMin + iMax) / 2;
int j = halfLen - 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;
}
}
复杂度分析
2019-4-25:后来发现这道题其实挺难的,补一下题解:
为了解决这个问题,我们需要理解“中位数的作用是什么”。在统计中,中位数被用来:
将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。
如果理解了中位数的划分作用,我们就很接近答案了。
首先,让我们在任一位置 i i i 将 A \text{A} A 划分成两个部分:
left_A | right_A
A[0], A[1], ..., A[i-1] | A[i], A[i+1], ..., A[m-1]
由于 A \text{A} A 中有 m m m 个元素, 所以我们有 m + 1 m+1 m+1 种划分的方法( i = 0 ∼ m i = 0 \sim m i=0∼m)。
我们知道:
len ( left _ A ) = i , len ( right _ A ) = m − i \text{len}(\text{left}\_\text{A}) = i, \text{len}(\text{right}\_\text{A}) = m - i len(left_A)=i,len(right_A)=m−i
注意:当 i = 0 i = 0 i=0 时, left _ A \text{left}\_\text{A} left_A 为空集, 而当 i = m i = m i=m 时, right _ A \text{right}\_\text{A} right_A 为空集。
采用同样的方式,我们在任一位置 j j j 将 B \text{B} B 划分成两个部分:
left_B | right_B
B[0], B[1], ..., B[j-1] | B[j], B[j+1], ..., B[n-1]
将 left _ A \text{left}\_\text{A} left_A 和 left _ B \text{left}\_\text{B} left_B 放入一个集合,并将 right _ A \text{right}\_\text{A} right_A 和 right _ B \text{right}\_\text{B} right_B 放入另一个集合。 再把这两个新的集合分别命名为 left _ part \text{left}\_\text{part} left_part 和 right _ part \text{right}\_\text{part} right_part:
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]
如果我们可以确认:
- len ( left _ part ) = len ( right _ part ) \text{len}(\text{left}\_\text{part}) = \text{len}(\text{right}\_\text{part}) len(left_part)=len(right_part)
- max ( left _ part ) ≤ min ( right _ part ) \max(\text{left}\_\text{part}) \leq \min(\text{right}\_\text{part}) max(left_part)≤min(right_part)
那么,我们已经将 { A , B } \{\text{A}, \text{B}\} {A,B} 中的所有元素划分为相同长度的两个部分,且其中一部分中的元素总是大于另一部分中的元素。那么:
median = max ( left _ part ) + min ( right _ part ) 2 \text{median} = \frac{\text{max}(\text{left}\_\text{part}) + \text{min}(\text{right}\_\text{part})}{2} median=2max(left_part)+min(right_part)
要确保这两个条件,我们只需要保证:
- i + j = m − i + n − j i + j = m - i + n - j i+j=m−i+n−j(或: m − i + n − j + 1 m - i + n - j + 1 m−i+n−j+1) 如果 n ≥ m n \geq m n≥m,只需要使 i = 0 ∼ m , j = m + n + 1 2 − i i = 0 \sim m, j = \frac{m + n + 1}{2} - i i=0∼m,j=2m+n+1−i
- B [ j − 1 ] ≤ A [ i ] \text{B}[j-1] \leq \text{A}[i] B[j−1]≤A[i]以及 A [ i − 1 ] ≤ B [ j ] \text{A}[i-1] \leq \text{B}[j] A[i−1]≤B[j]
ps.1 为了简化分析,我假设 A [ i − 1 ] , B [ j − 1 ] , A [ i ] , B [ j ] \text{A}[i-1], \text{B}[j-1], \text{A}[i], \text{B}[j] A[i−1],B[j−1],A[i],B[j]总是存在,哪怕出现 i = 0 i=0 i=0, i = m i=m i=m, j = 0 j=0 j=0,或是 j = n j=n j=n 这样的临界条件。 我将在最后讨论如何处理这些临界值。
ps.2 为什么 n ≥ m n \geq m n≥m?由于 0 ≤ i ≤ m 0 \leq i \leq m 0≤i≤m 且 j = m + n + 1 2 − i j = \frac{m + n + 1}{2} - i j=2m+n+1−i,我必须确保 j j j 不是负数。如果 n < m n < m n<m,那么 j j j 将可能是负数,而这会造成错误的答案。
所以,我们需要做的是:
在 [ 0 , m ] [0,m] [0,m] 中搜索并找到目标对象 i i i以使:
B [ j − 1 ] ≤ A [ i ] \qquad \text{B}[j-1] \leq \text{A}[i] B[j−1]≤A[i]且 $\ \text{A}[i-1] \leq \text{B}[j], $ 其中 j = m + n + 1 2 − i j = \frac{m + n + 1}{2} - i j=2m+n+1−i
接着,我们可以按照以下步骤来进行二叉树搜索:
当找到目标对象 i i i 时,中位数为:
max ( A [ i − 1 ] , B [ j − 1 ] ) , \max(\text{A}[i-1], \text{B}[j-1]), max(A[i−1],B[j−1]), 当 m + n m + n m+n 为奇数时
max ( A [ i − 1 ] , B [ j − 1 ] ) + min ( A [ i ] , B [ j ] ) 2 , \frac{\max(\text{A}[i-1], \text{B}[j-1]) + \min(\text{A}[i], \text{B}[j])}{2}, 2max(A[i−1],B[j−1])+min(A[i],B[j]), 当 m + nm+n 为偶数时
现在,让我们来考虑这些临界值 i = 0 , i = m , j = 0 , j = n i=0,i=m,j=0,j=n i=0,i=m,j=0,j=n,此时 A [ i − 1 ] , B [ j − 1 ] , A [ i ] , B [ j ] \text{A}[i-1],\text{B}[j-1],\text{A}[i],\text{B}[j] A[i−1],B[j−1],A[i],B[j]可能不存在。 其实这种情况比你想象的要容易得多。
我们需要做的是确保 max ( left _ part ) ≤ min ( right _ part ) \text{max}(\text{left}\_\text{part}) \leq \text{min}(\text{right}\_\text{part}) max(left_part)≤min(right_part)。 因此,如果 i i i 和 j j j 不是临界值(这意味着 A [ i − 1 ] , B [ j − 1 ] , A [ i ] , B [ j ] \text{A}[i-1], \text{B}[j-1],\text{A}[i],\text{B}[j] A[i−1],B[j−1],A[i],B[j] 全部存在), 那么我们必须同时检查 B [ j − 1 ] ≤ A [ i ] \text{B}[j-1] \leq \text{A}[i] B[j−1]≤A[i] 以及 A [ i − 1 ] ≤ B [ j ] \text{A}[i-1] \leq \text{B}[j] A[i−1]≤B[j] 是否成立。 但是如果 A [ i − 1 ] , B [ j − 1 ] , A [ i ] , B [ j ] \text{A}[i-1],\text{B}[j-1],\text{A}[i],\text{B}[j] A[i−1],B[j−1],A[i],B[j]中部分不存在,那么我们只需要检查这两个条件中的一个(或不需要检查)。 举个例子,如果 i = 0 i = 0 i=0,那么 A [ i − 1 ] \text{A}[i-1] A[i−1] 不存在,我们就不需要检查 A [ i − 1 ] ≤ B [ j ] \text{A}[i-1] \leq \text{B}[j] A[i−1]≤B[j] 是否成立。 所以,我们需要做的是:
在 [ 0 , m ] [0,m] [0,m] 中搜索并找到目标对象$ i$,以使:
( j = 0 j = 0 j=0 or i = m i = m i=m or B [ j − 1 ] ≤ A [ i ] ) \text{B}[j-1] \leq \text{A}[i]) B[j−1]≤A[i])) 或是 ( i = 0 i = 0 i=0 or j = n j = n j=n or A [ i − 1 ] ≤ B [ j ] \text{A}[i-1] \leq \text{B}[j] A[i−1]≤B[j]), 其中 j = m + n + 1 2 − i j = \frac{m + n + 1}{2} - i j=2m+n+1−i
在循环搜索中,我们只会遇到三种情况:
- ( j = 0 j = 0 j=0 or i = m i = m i=m or B [ j − 1 ] ≤ A [ i ] ) \text{B}[j-1] \leq \text{A}[i]) B[j−1]≤A[i]) 或是
( i = 0 i = 0 i=0 or j = n j = n j=n or A [ i − 1 ] ≤ B [ j ] ) \text{A}[i-1] \leq \text{B}[j]) A[i−1]≤B[j])
这意味着 i i i 是完美的,我们可以停止搜索。- j > 0 j > 0 j>0 and i < m i < m i<m and B [ j − 1 ] > A [ i ] \text{B}[j - 1] > \text{A}[i] B[j−1]>A[i]
这意味着 i i i 太小,我们必须增大它。- i > 0 i > 0 i>0 and j < n j < n j<n and A [ i − 1 ] > B [ j ] \text{A}[i - 1] > \text{B}[j] A[i−1]>B[j]
这意味着 i i i 太大,我们必须减小它。
感谢 @Quentin.chen 指出: i < m    ⟹    j > 0 i < m \implies j > 0 i<m⟹j>0 以及 i > 0    ⟹    j < n i > 0 \implies j < n i>0⟹j<n 始终成立,这是因为:
m ≤ n , i < m    ⟹    j = m + n + 1 2 − i > m + n + 1 2 − m ≥ 2 m + 1 2 − m ≥ 0 m \leq n, i < m \implies j = \frac{m+n+1}{2} - i > \frac{m+n+1}{2} - m \geq \frac{2m+1}{2} - m \geq 0 m≤n,i<m⟹j=2m+n+1−i>2m+n+1−m≥22m+1−m≥0
m ≤ n , i > 0    ⟹    j = m + n + 1 2 − i < m + n + 1 2 ≤ 2 n + 1 2 ≤ n m \leq n, i > 0 \implies j = \frac{m+n+1}{2} - i < \frac{m+n+1}{2} \leq \frac{2n+1}{2} \leq n m≤n,i>0⟹j=2m+n+1−i<2m+n+1≤22n+1≤n
所以,在情况 2 和 3中,我们不需要检查 j > 0j>0 或是 j < nj<n 是否成立。
简单为啥要说是困难
2019-4-25:当时的自己就是个傻X。。。一开始以为递归的去找中位数大的小的部分,中位数小的大的部分的中位数就可以解决。后来仔细想一下,其实这个思路得用个find Kth子方法递归的话才行。但这样感觉复杂度就达不到要求了。
感觉这道题的题解貌似就是《计算机算法:设计与分析导论》里面讲中位数的最快捷的求法,比findKth实现要更快?手边暂时没有《计算机算法:设计与分析导论》,没办法查看了。先看懂题解吧……比自己当时想象的厉害多了,怪不得是困难……