题目在leetcode上的链接为:
https://leetcode-cn.com/problems/median-of-two-sorted-arrays/
题目描述
解题思路
如果题目没有限制时间复杂度为 o(log(m+n))的话,那么就可以用归并排序的合并有序数组的方法解题,此时时间复杂度为 o(m+n)。要求时间复杂度o(log(m+n)),那么容易想到的就是使用二分法的思想解题。
首先我们要明确中位数的作用,中位数将一个数组划分为两个部分,这两个部分的特点是:
- 两个部分的长度相同
- 左边的元素都要小于等于右边的元素
回到题目,要求两个有序数组 A,B 的中位数,我们可以把 A 以索引 i 划分为两个部分,左边部分为 A[0] 到 A[i-1] ,右边部分为 A[i] 到 A[m-1](m为数组 A 的长度);把 B 以索引 j 划分为两个部分,左右部分为 B[0] 到 B[j-1],右边部分为 B[j] 到 B[n-1](n为数组 B 的长度);然后将 A,B 的左边进行组合为 left_part,将 A,B 的右边进行组合为 right_part。我们要找到中位数,需要寻找的 i,j 能满足以下两个条件:
- len(left_part)=len(right_part)
- max(left_part)<=min(right_part)
对于第1个条件,len(left_part)=i+j,为了同时考虑 m+n 可能为奇数或者偶数的情况,我们令 i+j=(m+n+1)/2,即 j=(m+n+1)/2-i。
当 m+n 为奇数时,中位数为max(left_part);
当 m+n 为偶数时,中位数为 (max(left_part)+min(right_part))/2。
对于第2个条件,max(left_part)=max(A[i-1],B[j-1]),min(right_part)=min(A[i],B[j])
要使得max(left_part)<=min(right_part),我们只需要使得
A[i-1]<=B[j] 且 B[j-1]<=A[i]
所以我们只需要在[0,m]内寻找 i,使得
A[i-1]<=B[j] 且 B[j-1]<=A[i],其中j=(m+n+1)/2-i
ps.此时需要注意一点,由于 j>=0,而 i 在[0,m]中,所以必须使得 m<=n。
要在[0,m]中查找满足条件的 i,我们可以使用二分查找法。
算法的具体步骤为:
- 初始化 iMin=0,iMax=m
- 在 iMin<=iMax的条件下循环查找 i,令i=(iMin+iMax)/2,j=(m+n+1)-i
- 如果 A[i-1]<=B[j] 且 B[j-1]<=A[i],那么查找到了 i,循环结束
如果 A[i-1]>B[j],那么说明 A[i-1] 太大了,为了减小 A[i-1] ,我们需要减小 i,即令 iMax=i-1,此时 j 会增大,有可能会满足 A[i-1]<=B[j] 的条件,并返回步骤2
如果 B[j-1]>A[i],说明 A[i] 太小了,为了增大 A[i],我们需要增大 i,即令iMin=i+1,此时 j 会减小,有可能会满足 B[j-1]<=A[i] 的条件,并返回步骤2 - 如果 m+n 为奇数,中位数为 max(left_part);
如果 m+n 为偶数,中位数为 (max(left_part)+min(right_part))/2
但是在上述步骤中,我们并没有考虑到 A[i-1],A[i],B[j-1],B[j]不存在的情况,即当 i=0,i=m,j=0,j=n的边界条件。下面我们来分析边界条件的影响。
如果i=0,则 A[i-1] 不存在,此时 A[i-1]<=B[j] 这个式子也不存在。那么 i=0 意味着什么呢?i=0 意味着我们把整个数组 A 都放到了 right_part 中,此时 max(left_part)=B[j-1],min(right_part)=min(A[i],B[j]),要满足 max(left_part)<=max(right_part) 的条件,只需要 B[j-1]<=A[i] 即可,也就是说当 i=0 是其实不需要考虑 A[i-1]<=B[j] 这个条件。也就是说,此时 i=0 且 B[j-1]<=A[i] 成立的话就已经寻找到了满足条件的 i。
所以当考虑了边界条件后,上述算法只需要改动第3步骤即可。
改动后的第3步骤为:
如果 (i=0 或 j=n 或 A[i-1]<=B[j]) 且 (j=0 或 i=m 或 B[j-1]<=A[i]),那么查找到了 i,循环结束
如果 i>0 且 j<n 且 A[i-1]>B[j],那么说明 A[i-1] 太大了,为了减小 A[i-1] ,我们需要减小 i,即令 iMax=i-1,此时 j 会增大,有可能会满足 A[i-1]<=B[j] 的条件,并返回步骤2
如果 j>0 且 i<m 且 B[j-1]>A[i],说明 A[i] 太小了,为了增大 A[i],我们需要增大 i,即令iMin=i+1,此时 j 会减小,有可能会满足 B[j-1]<=A[i] 的条件,并返回步骤2
复杂度分析:
由于是在 [0,m] 中使用二分法查找 i,所以时间复杂度为 o(log(min(m,n)))
由于只需要创建常数个新的变量,所以空间复杂度为o(1)
python代码:
class Solution(object):
def findMedianSortedArrays(self, A, B):
"""
:type A: List[int]
:type B: List[int]
:rtype: float
"""
m = len(A)
n = len(B)
# 保证 m <= n
if m > n:
temp = A
A = B
B = temp
t = m
m = n
n = t
iMin = 0
iMax = m
halfLen = (m + n + 1) // 2
while iMin <= iMax:
i = (iMin + iMax) // 2
j = halfLen - i
if i > 0 and j < n and A[i - 1] > B[j]:
iMax = i -1
elif i < m and j > 0 and B[j - 1] > A[i]:
iMin = i + 1
else:
# 此时已经寻找到了满足条件的i
# 寻找maxLeft
if i == 0:
maxLeft = B[j - 1]
elif j == 0:
maxLeft = A[i - 1]
else:
maxLeft = max(A[i -1], B[j - 1])
# 一定要先判断是奇数要直接返回,因为可能存在 m+n 为1时,这时如果再计算minRigh 就会内存溢出
if (m + n) % 2 == 1:
return maxLeft
# 寻找minRight
if i == m:
minRight = B[j]
elif j == n:
minRight = A[i]
else:
minRight = min(A[i], B[j])
return (maxLeft + minRight) / 2.0