给定两个大小为 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
1、使用归并的方法将两个有序数组合并为一个大的有序数组,其中间位置即为中位数,时间复杂度为O(m+n),空间复杂度为O(m+n)
2、不需要合并两个有序数组,只要找到中位数的位置即可。维护两个指针,初始时分别指向两个数组的下标 0 的位置,每次将指向较小值的指针后移一位(如果一个指针已经到达数组末尾,则只需要移动另一个数组的指针),直到到达中位数的位置。,时间复杂度为O(m+n),空间复杂度为O(1)
3、由于题目要求时间复杂度为O(log(m+n)),所以想到采用二分的方法,首先分别找到两数组的中间位置,将其分为两个堆,继续调整使得左堆始终小于右堆,找出左堆最大和右堆最小,然后再在两堆的边界返回答案。
from typing import List
class Solution:
# 思想就是利用总体中位数的性质和左右中位数之间的关系来把所有的数先分成两堆,找出左堆最大和右堆最小,然后再在两堆的边界返回答案
def findMedianSortedArrays(self, nums1 : List[int], nums2 : List[int]) -> float:
m = len(nums1)
n = len(nums2)
# 让nums2成为更长的那一个数组
if m > n:
nums1,nums2,m,n = nums2,nums1,n,m
#两个都为空的异常处理
if n == 0:
raise ValueError
imin,imax = 0,m
#二分
while (imin <= imax):
imid = imin + (imax-imin)//2
jmid = (m + n -2*imid)//2
# 左堆最大的只有可能是nums1[imid-1],nums2[jmid-1]
# 右堆最小只有可能是nums1[imid],nums2[jmid]
# 让左右堆大致相等需要满足的条件是imid+jmid = m-imid+n-jmid 即 jmid = (m+n-2imid)//2
# 为什么是大致呢?因为有总数为奇数的情况,这里用向下取整数操作,所以如果是奇数,右堆会多1
if(imid > 0 and nums1[imid-1] > nums2[jmid]):
#数组一左堆最大大于数组二右堆最小,imid太大了
imax = imid - 1
elif(imid < m and nums1[imid] < nums2[jmid-1]):
# 数组二左堆最大大于数组一右堆最小,imid太小了
imin = imid + 1
else:
# 边界处理,依次得到左堆最大和右堆最小
if(imid == m) : minright = nums2[jmid]
elif(jmid == n) : minright = nums1[imid]
else:
minright = min(nums1[imid],nums2[jmid])
if(imid == 0) : maxleft = nums2[jmid-1]
elif(jmid == 0) : maxleft = nums1[imid-1]
else:
maxleft = max(nums1[imid-1],nums2[jmid-1])
#如果是奇数个,由于取中间数时向下取整,所以右堆多一,minright就是中位数
if ((m+n)%2) == 1:
return minright
#否则,取左堆最大和右堆最小的平均值
return (maxleft+minright)/2
solution = Solution()
nums1 = [1, 2]
nums2 = [3, 4]
print(solution.findMedianSortedArrays(nums1,nums2))
此外,参考官方给的题解,这道题可以转化成寻找两个有序数组中的第 k 小的数。
假设两个有序数组分别是 A 和 B。要找到第 k个元素,我们可以比较 A[k/2−1] 和 B[k/2−1],其中 /表示整数除法。由于 A[k/2−1] 和 B[k/2−1] 的前面分别有 A[0 .. k/2−2]和 B[0 .. k/2−2],即 k/2−1元素,对于 A[k/2−1]和 B[k/2−1] 中的较小值,最多只会有 (k/2−1)+(k/2−1)≤k/2−2个元素比它小,那么它就不能是第 k 小的数了。
因此我们可以归纳出三种情况:
如果 A[k/2−1]
如果 A[k/2−1]>B[k/2−1],则可以排除 B[0] 到 B[k/2−1]。
如果 A[k/2−1]=B[k/2−1],则可以归入第一种情况处理。
可以看到,比较 A[k/2−1] 和 B[k/2−1]之后,可以排除 k/2 个不可能是第 k 小的数,查找范围缩小了一半。同时,我们将在排除后的新数组上继续进行二分查找,并且根据我们排除数的个数,减少 k 的值,这是因为我们排除的数都不大于第 k 小的数。
有以下三种情况需要特殊处理:
如果 A[k/2−1] 或者 B[k/2−1]越界,那么我们可以选取对应数组中的最后一个元素。在这种情况下,我们必须根据排除数的个数减少 kkk 的值,而不能直接将 k 减去 k/2。
如果一个数组为空,说明该数组中的所有元素都被排除,我们可以直接返回另一个数组中第 k 小的元素。
如果 k=1,我们只要返回两个数组首元素的最小值即可。