跳到正文
第 4 题困难

寻找两个正序数组的中位数

Median of Two Sorted Arrays

求两个升序数组的中位数,要求 O(log(m+n));不允许直接把两个数组合并后排序。

在力扣中练习

题目描述

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

算法的时间复杂度应该为 O(log (m+n)) 。

 

示例 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

 

 

提示:

  • nums1.length == m
  • nums2.length == n
  • 0 <= m <= 1000
  • 0 <= n <= 1000
  • 1 <= m + n <= 2000
  • -106 <= nums1[i], nums2[i] <= 106

解题思路

主解法

二分分割 · 只切不合并

在较短数组上找一条切线,使左右两半元素个数相等且交叉有序

中位数把有序序列分成左右两半。与其合并,不如在两个数组上各切一刀:nums1 切在 i,nums2 切在 j,令左半部分一共 (m+n+1)/2 个元素,即 j = (m+n+1)/2 - i。

切得对不对,只看四个边界值:aLeft、aRight、bLeft、bRight。合法条件是

aLeft <= bRight  且  bLeft <= aRight

也就是说,左半部分的最大值不超过右半部分的最小值。

图解
nums1 · 左 1 个
0
1
2
3
4
1
9
10
11
12
i
分割边界
aLeft=1aRight=9bLeft=5bRight=6分割合法
nums2 · 左 4 个
0
1
2
3
4
2
3
4
5
6
j
普通节点当前操作已确认 / 结果待处理 / 对照
切点把两个数组分成左右两部分;合法分割要求左半最大值不超过右半最小值。

如果 aLeft > bRight,说明 nums1 切得太靠右,i 要左移;否则说明切得太靠左,i 要右移。i 的取值是单调的,所以可以二分。

分步图解

切在 nums1[2]、nums2[3]:左边界 9 / 4,右边界 10 / 5;aLeft 太大,切点要左移。

i
2
j
3
nums1 · 左 2 个
0
1
2
3
4
1
9
10
11
12
i
分割边界
aLeft=9aRight=10bLeft=4bRight=5需要调整
nums2 · 左 3 个
0
1
2
3
4
2
3
4
5
6
j
1 / 3
i=2 时 aLeft 大于 bRight,切点左移;i=1 时四个边界交叉条件成立。

自拟数组第一次切在 i=2,j=3 时 aLeft=9 大于 bRight=6,不合法;调整到 i=1,j=4 后四个边界满足交叉条件,左半最大 5、右半最小 6,中位数是 5.5。

i 在较短数组上枚举,范围只有 0..m,因此时间 O(log min(m,n)),空间 O(1)。

时间 O(log min(m,n))空间 O(1)

易错点

  • 在长数组上二分:i 的范围大,j 可能越界。
  • j 的公式写成 (m+n)/2-i:奇数长度时左半会少一个元素。
  • 切点在边界时不使用 ±∞:i=0 或 i=m 时会访问 nums[-1]。
  • 偶数是直接返回 max(aLeft,bLeft):还要求和右半最小值取平均。
对照解法

归并 · 合并后取中位

像归并排序一样合并两个数组,再取中间

结果正确,但需要 O(m+n) 时间与 O(m+n) 空间,不满足题目的 O(log) 要求;仅作直觉对照。

最直白的做法:用双指针把两个有序数组合并成一个 all,然后按下标取中位数。长度是奇数时取 all[n/2],偶数时取中间两个的平均值。

它的优点是绝不会写错边界,适合用来验证二分分割版的结果。缺点是时间和空间都是 O(m+n),面试中如果题目明确要求 O(log(m+n)),这个方案不满足要求。

实际工程里如果数据量不大,归并版反而更稳妥;但在算法题里要明确说明它的复杂度代价。

  1. 1双指针合并两个有序数组。
  2. 2按总长度奇偶取中位数。
  3. 3偶数长度取中间两个的平均。
时间 O(m+n)空间 O(m+n)

易错点

  • 只合并到中位数就停止时下标算错:可以先合并全部,逻辑最简单。
  • 偶数长度用整数除法:会丢掉 0.5。

代码与演示

主解法配有图解;对照解法用于比较思路、复杂度或实现方式。

演示输入nums1 = [1,9,10,11,12],nums2 = [2,3,4,5,6]

把两个数组各切一刀,使左半部分一共 (m+n+1)/2 个元素;中位数只由切点两侧的四个值决定。

nums1 · 左 ? 个
0
1
2
3
4
1
9
10
11
12
分割边界
尚未分割
nums2 · 左 ? 个
0
1
2
3
4
2
3
4
5
6
1 / 3
查看 C++ 代码

延伸阅读

  • “第 k 小”问题可以在两个有序数组上二分,每次排除 k/2 个元素;中位数是它的特例。
  • 分割法的四个边界值需要用 ±∞ 处理空侧,这是写对边界的关键。