题目描述
给定两个大小为 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
求解思路
【注】思路主要是给本人自己看的,以便忘了能记起。
首先,我们需要理解 “中位数的作用是什么”。在统计中,中位数被用来:
将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。
如果是两个等长的数组,那么我们可以分别求2个数组的中位数,比较大小后,各自删除一半,反复如此,直到两边数组都只剩一个元素为止,最终中位数就是这两个元素的平均数。
然而,此题没要要求两个数组长度必须一致,难就难在这里,所以只能模拟枚举。整体的中位数把元素分割成left\bf \blue {left}left和right\bf \blue{right}right两个集合,我们就要探测在这两个集合中nums1和nums2数组各有多少个元素在里面。
简单的方法是类似合并两个有序列表的算法,时间复杂度是O(min(m,n))O(min(m,n))O(min(m,n)),但是题目要求的是O(log(m+n))O(log(m+n))O(log(m+n)),显然是要用到递归,不然时间复杂度不可能出现对数。
怎么用递归呢,参考两个数组长度相等找中位数的算法,每次删去一部分,直到最后剩下结果。现在数组长度不相等了,这种二分元素显然行不通,所以退而求其次,二分索引,并且是长度较小的数组的索引。为什么是较短的数组?在后面编码时你会发现,探测长数组的索引可能会导致一些麻烦,需要额外的工作。
那么,二分所引怎么用呢?通常情况,是设置两个哨兵i_maxi\_maxi_max和i_mini\_mini_min,探测位置i=func(i_min,i_max)i=func(i\_min,i\_max)i=func(i_min,i_max),通过判断iii是偏大还是偏小,来调整i_maxi\_maxi_max或i_mini\_mini_min值,然后继续这一步,直到找到满足中位数要求的iii。
-------------------------------------下面开始具体算法-------------------------------------
首先,根据数组长度进行排序,令短的为nums1,长的为nums2。m = len(nums1), n = len(nums2)
我们将nums1分成两个部分,左边iii个元素,右边m−im-im−i个元素,其中0≤i≤m0\le i \le m0≤i≤m:
left_of_nums1\bf{left\_of\_nums1}left_of_nums1 | right_of_nums1\bf{right\_of\_nums1}right_of_nums1 | |
---|---|---|
i=0i=0i=0 | None | nums1 |
0<i<m0<i<m0<i<m | nums1[0],nums1[1],…,nums1[i-1] | nums1[i],nums1[i+1],…,nums2[m-1] |
i=mi = mi=m | nums1 | None |
同理,将nums2分成两个部分,左边jjj个元素,右边n−in-in−i个元素:
left_of_nums2\bf{left\_of\_nums2}left_of_nums2 | right_of_nums2\bf{right\_of\_nums2}right_of_nums2 | |
---|---|---|
j=0j=0j=0 | None | nums2 |
0<j<m0<j<m0<j<m | nums2[0],nums2[1],…,nums2[j-1] | nums2[j],nums2[j+1],…,nums2[n-1] |
j=mj = mj=m | nums2 | None |
将left_of_nums1\bf{left\_of\_nums1}left_of_nums1和left_of_nums2\bf{left\_of\_nums2}left_of_nums2放入集合left\bf \blue {left}left,right_of_nums1\bf{right\_of\_nums1}right_of_nums1和right_of_nums2\bf{right\_of\_nums2}right_of_nums2放入集合right\bf \blue {right}right,如果我们可以确认:
① len(left)=len(right) 或者len(left) = len(right)+1len(\blue {left}) = len( \blue {right}) \ 或者len(\blue {left})\ =\ \ len( \blue {right})+1len(left)=len(right) 或者len(left) = len(right)+1
② max(left)≤min(right)max(\blue {left}) \le min( \blue {right})max(left)≤min(right)
那么,我们已经将 nums1和nums2中的所有元素划分为相同长度的两个部分,且其中一部分中的元素总是大于另一部分中的元素
那么:
median=max_of_left+min_of_right2\bf \blue {median} = \frac{\blue {max\_of\_left} + \blue {min\_of\_right}}{2}median=2max_of_left+min_of_right
要确保上面两个条件,我们只需保证:
① count_of_left=len(left)=⌈m + n2⌉\bf \blue {count\_of\_left} = len(\blue {left}) = \lceil \frac{m\ +\ n}{2}\rceilcount_of_left=len(left)=⌈2m + n⌉
② i + j = count_of_lefti\ +\ j\ =\ \bf \blue {count\_of\_left}i + j = count_of_left
③ max(max(max(nums1[i-1]
,nums2[j-1]
)≤min() \le min()≤min(nums[i]
,nums2[j]
)))
【注】边界情况为简单情况,单独讨论即可,上面为一般情况下的做法
结合之前所说,可以这么做:
把上面要满足的条件②放到else语句里,这样,可以把特殊情况的解答也放在else里,减少代码冗余;而if语句用来调整i_mini\_mini_min和i_maxi\_maxi_max的值.
Python3解答
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
m, n = len(nums1), len(nums2)
# 这里为了保证nums1一定是长度较小的数组
if m > n:
nums1, nums2, m, n = nums2, nums1, n, m
# 题目给定数组不会同时为空,也就是m^2+n^2≠0,由于m≤n,故只要n≠0即可
if not n:
raise ValueError("数组长度不同时为零")
i_min, i_max = 0, m
# left集合元素数量,如果m+n是奇数,left比right多一个数据
count_of_left = (m + n + 1) // 2
while i_min <= i_max:
i = (i_min + i_max) // 2 # left有i个nums1的元素
j = count_of_left - i # left有j个nums2的元素
if i > 0 and nums1[i - 1] > nums2[j]:
i_max = i - 1 # i太大,要减少
elif i < m and nums1[i] < nums2[j - 1]:
i_min = i + 1 # i太小,要增加
else:
if i == 0:
max_of_left = nums2[j - 1]
elif j == 0:
max_of_left = nums1[i - 1]
else:
max_of_left = max(nums1[i - 1], nums2[j - 1])
if (m + n) % 2:
return float(max_of_left) # 结果是浮点数
if i == m:
min_of_right = nums2[j]
elif j == n:
min_of_right = nums1[i]
else:
min_of_right = min(nums1[i], nums2[j])
return (max_of_left + min_of_right) / 2.0 # 结果为浮点数