4.高效查找两个正序数组的中位数:O(log(m+n))算法详解
问题引入
- 寻找两个正序数组的中位数
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。

中位数的定义与问题边界
中位数是将数据分为两部分的中间值,需满足:
- 长度平衡:左右两部分长度相等或相差1
- 数值有序:左部所有元素 ≤ 右部所有元素
对于正序数组,无需合并即可高效查找中位数。输出规则:
- 奇数长度:中间位置的单个数值
- 偶数长度:中间两个数的平均值(需保留浮点数精度)
时间复杂度的严格限制
当总长度为1e6时:
- 暴力解法(O(m+n)):需100万次操作
- 二分查找(O(log(m+n))):仅需约20次操作
这种差距在大数据场景下至关重要,必须利用数组有序性实现对数级复杂度。
示例驱动的直观理解
示例1:nums1 = [1,3], nums2 = [2]
合并后[1,2,3],中位数为2(奇数长度)
示例2:nums1 = [1,2], nums2 = [3,4]
合并后[1,2,3,4],中位数为(2+3)/2=2.5(偶数长度)
合并数组法虽然直观,但存在O(m+n)的时间和空间开销,必须寻找更优解。
从暴力解法到二分查找的思路演进
暴力解法:合并数组法的局限
合并过程类似归并排序:
def merge_and_find(nums1, nums2):
merged = []
i = j = 0
while i < len(nums1) and j < len(nums2):
if nums1[i] < nums2[j]:
merged.append(nums1[i])
i += 1
else:
merged.append(nums2[j])
j += 1
merged.extend(nums1[i:])
merged.extend(nums2[j:])
L = len(merged)
return merged[L//2] if L%2 else (merged[L//2-1]+merged[L//2])/2
缺陷:
- 时间复杂度O(m+n),空间复杂度O(m+n)
- 大数据场景下内存占用和运算时间不可接受
关键洞察:分割数组找中位数
核心思想:找到分割点(i,j),使:
- 左部元素总数 = (m+n+1)//2(确保左部可能多1个元素)
- 左部最大值 ≤ 右部最小值
此时:
- 奇数长度:中位数 = 左部最大值
- 偶数长度:中位数 = (左部最大值+右部最小值)/2
二分查找的引入
选择较短数组进行二分(时间复杂度O(log min(m,n))):
- 分割点i(nums1)和j(nums2)满足i+j=(m+n+1)//2
- 通过二分调整i,使nums1[i-1] ≤ nums2[j]且nums2[j-1] ≤ nums1[i]
二分查找算法的核心原理
分割点的数学约束
设总长度L=m+n,左部需有(L+1)//2个元素,因此:
i + j = (m + n + 1) // 2
示例验证:
- 示例1:m=2,n=1 → i+j=(3+1)//2=2
- 示例2:m=2,n=2 → i+j=(4+1)//2=2
分割条件与调整策略
核心条件:
- 左部最大值 = max(nums1[i-1], nums2[j-1])
- 右部最小值 = min(nums1[i], nums2[j])
- 需满足:左部最大值 ≤ 右部最小值
调整规则:
- 若nums1[i-1] > nums2[j] → i过大,需左移
- 若nums2[j-1] > nums1[i] → i过小,需右移
边界处理与哨兵值技巧
使用±∞处理边界情况:
- i=0(nums1左部为空)→ nums1[i-1] = -∞
- i=m(nums1右部为空)→ nums1[i] = +∞
- j=0或j=n时同理
代码实现:
max_left = max(nums1[i-1] if i>0 else -inf, nums2[j-1] if j>0 else -inf)
min_right = min(nums1[i] if i<m else inf, nums2[j] if j<n else inf)
Python代码实现与关键细节
完整代码与注释
def findMedianSortedArrays(nums1, nums2):
# 确保nums1是较短数组
if len(nums1) > len(nums2):
nums1, nums2 = nums2, nums1
m, n = len(nums1), len(nums2)
left, right = 0, m # 二分查找范围
while left <= right:
i = (left + right) // 2 # nums1分割点
j = (m + n + 1) // 2 - i # nums2分割点
# 处理边界情况
nums1_left_max = nums1[i-1] if i > 0 else float('-inf')
nums1_right_min = nums1[i] if i < m else float('inf')
nums2_left_max = nums2[j-1] if j > 0 else float('-inf')
nums2_right_min = nums2[j] if j < n else float('inf')
# 判断分割是否有效
if nums1_left_max <= nums2_right_min and nums2_left_max <= nums1_right_min:
# 计算中位数
if (m + n) % 2 == 1:
return max(nums1_left_max, nums2_left_max)
else:
return (max(nums1_left_max, nums2_left_max) +
min(nums1_right_min, nums2_right_min)) / 2
elif nums1_left_max > nums2_right_min:
right = i - 1 # i过大,左移
else:
left = i + 1 # i过小,右移
raise ValueError("Input arrays are not sorted")
关键细节解析
- 数组交换:确保在较短数组上二分,优化时间复杂度
- 分割点计算:i和j的关系保证左右部长度平衡
- 哨兵值:用±∞简化边界判断,避免索引越界
- 二分调整:通过比较边界值动态调整i,找到合法分割点
示例验证与执行流程
示例1:奇数长度
输入:nums1=[1,3], nums2=[2]
预处理:交换后nums1=[2], nums2=[1,3](m=1,n=2)
二分过程:
- i=(0+1)//2=0 → j=2-0=2
- max_left = max(-inf, nums2[1])=3? 修正后nums2=[1,2] → max_left=2
- min_right = min(2, inf)=2
- 中位数=2(奇数长度)
示例2:偶数长度
输入:nums1=[1,2], nums2=[3,4](m=2,n=2)
二分过程:
- i=(0+2)//2=1 → j=2-1=1
- nums1_left_max=1, nums2_right_min=4(1≤4)
- nums2_left_max=3, nums1_right_min=2(3>2 → i过小)
- left=2 → i=2, j=0
- max_left=2, min_right=3 → 中位数=(2+3)/2=2.5
算法复杂度分析
时间复杂度:O(log min(m,n))
- 在较短数组上二分,迭代次数为log min(m,n)
- min(m,n) ≤ m+n → log min(m,n) ≤ log(m+n),满足题目要求
空间复杂度:O(1)
- 仅使用常数级变量,不依赖输入规模
435

被折叠的 条评论
为什么被折叠?



