现给两个已排序的数组nums1和nums2,长度分别为m和n
求两个数组合并后的中位数,要求复杂度为O( log( m+n ) )
- 例1:
nums1 = [1, 3]
nums2 = [2]
The median is 2.0
- 例2:
nums1 = [1, 2]
nums2 = [3, 4]
The median is (2 + 3)/2 = 2.5
虽然二分的复杂度是O( log(n) ),但合并的复杂度是O( n ),只能找trick咯…
题解
为了解决这个问题,首先要理解中位数有什么用
在统计学中,中位数用于:“把一个集合分成两个长度相等的子集,其中一个子集的任何一个元素总是大于另一个子集的任何元素(即一个子集中最小的元素大于另一个子集中最大的元素)”
理解了这个,我们就非常接近问题的答案了。
设集合A、B长度分别为m、n
取随机数i满足0<=i<=m , j满足0<=j<=n
首先我们在i位置把A分成左右两部分:
left_A | right_A
A[0], A[1], ..., A[i-1] | A[i], A[i+1], ..., A[m-1]
因为A有m个元素,所以有m+1种分法 (i = 0 ~ m)
同时我们知道:len(left_A) = i, len(right_A) = m - i
同样,我们在j位置把B分成左右两部分:
left_B | right_B
B[0], B[1], ..., B[j-1] | B[j], B[j+1], ..., B[n-1]
我们把left_A和left_B放在一起称为left_part,同样把right_A和right_B放在一起称为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]
只要我们保证下面两个条件,集合A、B合并之后得到的集合{A, B}就被分成了两个子集left_part和right_part
中位数为 ( max(left_part) + min(right_part) )/2
1) len(left_part) == len(right_part)//长度相等
2) max(left_part) <= min(right_part)//大小关系
1)
len( left_part ) == i + j
len( right_part) == m - i + n - j(或 m-i+n-j+1)
2)
A[i-1] <= A[i] 、B[j-1] <= B[j]
B[j-1] <= A[i] 、A[i-1] <= B[j]
为了满足这两个条件,只需要保证:
(1) i + j == m - i + n - j //m+n为偶数
i + j == m - i + n - j + 1//m+n为奇数,右边+1就把中位数放在了left_part
(2) B[j-1] <= A[i] and A[i-1] <= B[j]
//因为序列已排序,A[i-1] <= A[i] and B[j-1] <= B[j]已成立
注解:
1、暂时只考虑通常情况,边界情况我们之后再讨论
2、为了方便起见,我们保证n >= m,这样就有:i = 0~m ,j = (m + n + 1)/2 - i //否则j可能为负
所以:
在保证n >= m的情况下,我们使i = 0~m , 则j = (m + n + 1)/2 - i ,此时i、j满足条件(1)。
在此前提下我们只需要找到合适的i,使得B[j-1] <= A[i] 、 A[i-1] <= B[j]即可。
下面给出使用二分法搜索i的伪代码:
<1> imin = 0, imax = m 我们在[imin , imax]区间进行搜索
<2> i = (imin + imax)/2, j = (m + n + 1)/2 - i
<3> 此时i、j满足条件(1),而且我们只会遇到以下三种情况:
- a) B[j-1] <= A[i] and A[i-1] <= B[j]
- 条件(2)满足,我们找到了合适的i
- b) B[j-1] > A[i]
- 此时A[i]较小,需要增大A[i]
- 由于序列已排序,A[i]与i正相关,所以我们调整搜索区间为[i+1, imax]
- goto<2>
- c) A[i-1] > B[j]
- 同理,我们调整搜索区间为[imin, i-1]
- goto<2>
注解:
B[j-1] > A[i]和A[i-1] > B[j]不会同时满足,因为A[i] > A[i-1]、B[j] > B[j-1]
此时我们已经找到了合适的i,所以中位数为:
max(A[i-1], B[j-1])//m+n为奇数,设置j的值时我们把它放在了left_part
(max(A[i-1], B[j-1]) + min(A[i], B[j]))/2 //m+n为偶数,由于(m + n + 1)/2 == (m+n)/2
好了,是时候讨论边界情况了:
当取 i=0,i=m,j=0,j=n 时 A[i-1],B[j-1],A[i],B[j]可能不存在
事实上边界情况比想象中的容易处理:
当i、j的取值满足条件(1)时,我们还要做的是保证max(left_part) <= min(right_part)
所以:当i、j不是边界值的时候我们需要保证B[j-1] <= A[i] 和A[i-1] <= B[j];而当A[i-1],B[j-1],A[i],B[j]中的某个不存在时,我们只要不考虑它就可以了。例如i=0时A[i-1]不存在,我们不考虑A[i-1] <= B[j],只要B[j-1] <= A[i]就保证了max(left_part) <= min(right_part)
由此,我们将合适的i的条件修改为:
(j == 0 or i == m or B[j-1] <= A[i]) and (i == 0 or j == n or A[i-1] <= B[j])
最后,给出代码:
def median(A, B):
m, n = len(A), len(B)
if m > n:
A, B, m, n = B, A, n, m
if n == 0:
raise ValueError
imin, imax, half_len = 0, m, (m + n + 1) / 2
while imin <= imax:
i = (imin + imax) / 2
j = half_len - i
if i < m and B[j-1] > A[i]:
# i is too small, must increase it
imin = i + 1
elif i > 0 and A[i-1] > B[j]:
# i is too big, must decrease it
imax = i - 1
else:
# i is perfect
if i == 0: max_of_left = B[j-1]
elif j == 0: max_of_left = A[i-1]
else: max_of_left = max(A[i-1], B[j-1])
if (m + n) % 2 == 1:
return max_of_left
if i == m: min_of_right = B[j]
elif j == n: min_of_right = A[i]
else: min_of_right = min(A[i], B[j])
return (max_of_left + min_of_right) / 2.0
完结撒花~