寻找两个正序数组的中位数题解——划分数组法
题目内容
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数。
# 示例 1:
# 输入
>>>nums1 = [1,3], nums2 = [2]
# 输出
>>>2.00000
# 合并数组 = [1,2,3] ,中位数 2
# 示例 2:
# 输入
>>>nums1 = [1,2], nums2 = [3,4]
# 输出
>>>2.50000
# 合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
# 示例 3:
# 输入
>>>nums1 = [], nums2 = [1]
# 输出
>>>1.00000
# 输入
>>>nums1 = [2], nums2 = []
# 输出
>>>2.00000
提示:
0 <= m <= 1000 , 0 <= n <= 1000
1 <= m + n <= 2000
-1000000 <= nums1[i], nums2[i] <= 1000000
解题方法
看到这个题我们首先要思考,中位数的作用是什么。
将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。
我们将nums1与nums2分别在 i,j 的位置划分为左右两部分试试看:
# 假设: m, n = len(nums1), len(nums2)
left | right
nums1[0, ... , i-1] | nums1[i, ... , m-1]
nums2[0, ... , j-1] | nums2[j, ... , n-1]
若把两个数组的左半边合并成一个新的集合:left,右半边合并成另一个新的集合:right
我们希望能找到一种划分方式,使left与right集合恰好是中位数划分出的左右两子集
为此我们需要满足两个条件:
1.left集合的最大元素小于right集合的最小元素
2.left集合的长度与right集合的长度相同
# 条件1:
# left集合最大元素为 max(nums1[i-1], nums2[j-1])
# right集合最小元素为 min(nums1[i], nums2[j])
# 由于在划分阶段,我们需要遍历较小长度的数组以寻找最优方案
# 所以仅控制nums1[i-1]即可
# nums1[i-1] <= nums1[i] # 由于是正序数组,该条件一定满足
nums1[i-1] <= nums2[j]
考虑到总长度的奇偶性,我们要考虑更详细完善的约束条件:
1.若总长度为偶数,中位数实际上由中间的两个数共同组成,上述条件不用更改
2.若总长度为奇数,我们将中位数放入left集合,使left集合比right集合多一个元素,上述条件需要修正
# 条件2:
# left集合长度为: i + j
# 两正序数组总长度为: m + n
# 当总长度为偶数时:
if (m + n) % 2 == 0:
j = (m + n) / 2 - i
# 当总长度为奇数时:
if (m + n) % 2 == 1
j = (m + n + 1) / 2 - i
# 我们发现可以将奇偶情况下的约束合并为一条语句,省去判奇偶的时间:
j = (m + n + 1) // 2 - i
为了更快的找到划分的位置,我们可以采用二分查找的方法,快速找到 i 的位置:
# 借助双指针left与right来计算 i 的位置
i = (left + right) // 2
# 在最初状态,i 应该在nums1中间
left, right = 0, m
# 若 i 取大了, 将right指针移至 i 的左边:
若i太大:
right = i - 1
# 若 i 取小了, 将left指针移至 i 的右边:
若i太小:
left = i + 1
找到合适的划分方式后,根据总长度的奇偶情况返回合适的数即可
代码实现
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
if len(nums1) > len(nums2):
return self.findMedianSortedArrays(nums2, nums1)
MAXNUM = 10**6
m = len(nums1)
n = len(nums2)
# 左右指针,寻找nums1划分点
left, right = 0, m
# 前半部分中位数, 后半部分中位数
median1, median2 = 0, 0
# nums1划分点 + nums2划分点
i, j = 0, 0
numsim1, numsi, numsjm1, numsj = 0, 0, 0, 0
while left <= right:
i = (left + right) // 2
j = (m + n + 1) // 2 - i
# nums1[0...i-1], nums1[i...m-1]
numsim1 = ( nums1[i-1] if i != 0 else -MAXNUM )
numsi = ( nums1[i] if i != m else MAXNUM )
# nums2[0...j-1], nums2[j...n-1]
numsjm1 = ( nums2[j-1] if j != 0 else -MAXNUM )
numsj = ( nums2[j] if j != n else MAXNUM )
if numsim1 <= numsj:
median1, median2 = max( numsim1, numsjm1), min(numsi, numsj)
left = i + 1
else:
right = i - 1
return (median1 + median2) / 2 if (m + n) % 2 == 0 else median1
复杂度分析
时间复杂度:O(logmin(m,n)))。
其中 m 和 n 分别是数组nums1 和 nums2 的长度。查找的区间是[0,m],而该区间的长度在每次循环之后都会减少为原来的一半。所以,只需要执行 logm 次循环。由于每次循环中的操作次数是常数,所以时间复杂度为 O(logm)。由于我们可能需要交换 nums1 和 nums2 使得m≤n,因此时间复杂度是 O(logmin(m,n)))。
空间复杂度:O(1)。
执行结果

欢迎各位大佬交流讨论
本文参考leecode题库第4题官方题解