题目描述:给定两个大小为 m 和 n 的有序数组 A 和 B 。找出这两个有序数组的中位数,并且要求算法时间复杂度为 O(log(m+n))。可以假设这两个数组不会同时为空。
示例 1:
输入: A = [1,3], B = [2]
输出: 2.0
示例 2:
输入: A = [1,2], B = [3,4]
输出: (2+3) / 2 = 2.5
其实如果没有时间复杂度的要求,这个问题用 Python 实在是太简单了,感觉有点像作弊:
def findMedianSortedArrays(A, B):
res = []
res.expand(A)
res.expand(B)
res = sorted(res)
if len(res) % 2 == 0:
half = len(res) // 2
return (res[half-1] + res[half]) / 2.0
else:
return res[len(res)//2] / 1.0
但是这个代码在效率上是不合格的,因为只是使用 sorted 函数 O 就已经爆掉了。看到给出的示例大概是这样的:
def findMedianSortedArrays(A, B):
m, n = len(A), len(B)
if m > n:
A, B, m, n = B, A, n, m
if n < 1:
raise ValueError
imin, imax, half = 0, m, (m+n+1) // 2
while imin <= imax:
i = (imin+imax) // 2
j = half - i
if i < m and A[i] < B[j-1]:
imin = i + 1
elif j > 0 and B[j] < A[i-1]:
imax = i - 1
else:
if i == 0: max_left = B[j-1]
elif j == 0: max_left = A[i-1]
else: max_left = max(A[i-1], B[j-1])
if (m+n) % 2 != 0:
return max_left / 1.0
else:
if i == m: min_right = B[j]
elif j == n: min_right = A[i]
else: min_right = min(A[i], B[j])
return (max_left+min_right) / 2.0
官方给出的示例解释非常专业,所以我看了很久才看懂。这里简单解释一下这个算法的思想:
- 所谓中位数的含义就是将一个集合划分成两个等长的子集,使得其中一个子集中的最大值小于另一个子集中的最小值,这样就得到了中位数的两个判据:等长划分、最值关系
- 首先找到两个分割点 i 和 j 分别分割 A 和 B, 保证分割后的 l e n ( A l e f t ) + l e n ( B l e f t ) len(A_{left}) + len(B_{left}) len(Aleft)+len(Bleft)与 l e n ( A r i g h t ) + l e n ( B r i g h t ) len(A_{right}) + len(B_{right}) len(Aright)+len(Bright)相等,这点由代码的10、11行保证,即保证了等长划分。
- 之后要保证两部分最值间的关系,因为原始的 A 和 B 都是有序的,所以只需沿着划分点比较就可以了,这省了很多事。最后确定的划分点应该满足
A [ i − 1 ] < B [ j ] , B [ j − 1 ] < A [ i ] A[i-1]<B[j],B[j-1]<A[i] A[i−1]<B[j],B[j−1]<A[i]
发现不满足,就根据不满足的情况调整 i 的搜索区间,就是代码中的二分查找。 - 最后解决边界问题,即 i = 0, m,j = 0, n 的情况,分类讨论一下即可。最后就找到了左部分的最大值和右部分的最小值,输出平均数作为返回值。