Hot 100 --- 寻找两个正序数组的中位数
本文概览:本文讲解寻找两个正序数组的中位数的核心思路:不需要合并数组,只需要在较短数组上二分切割,保证左边元素都小于右边元素
一、题目

二、题目分析
1. 题目要求
题目给定两个正序数组 nums1 和 nums2,要求找到这两个数组的中位数,时间复杂度 O(log(m+n))。
中位数的定义:
- 如果数组长度是奇数,中位数是中间那个数
- 如果数组长度是偶数,中位数是中间两个数的平均值
比如 [1, 2, 3, 4, 5] 的中位数是 3,[1, 2, 3, 4] 的中位数是 (2+3)/2 = 2.5。
2. 普通做法:双指针合并
最直观的思路:把两个数组合并成一个有序数组,然后直接找中位数。
怎么合并?用双指针,一个指针指向 nums1,一个指向 nums2,每次比较两个指针指向的元素,谁小谁就往后移,直到遍历到中间位置。
1 | nums1 = [1, 3, 5, 7] |
时间复杂度:O(m+n),因为需要遍历到中间位置。
问题:不符合题目要求的 O(log(m+n))。
3. 优化思路:从 O(m+n) 到 O(log(m+n))
看到 O(log n) 的时间复杂度,第一反应就是二分查找。
但怎么二分?两个数组,好像找不到切入点。
关键观察:我们不需要排序数组,只需要找到符合要求的前 mid 项元素。
比如 nums1 = [1, 3, 5, 7] 和 nums2 = [2, 4, 6],合并后是 [1, 2, 3, 4, 5, 6, 7],总长度 7,mid = (7+1)/2 = 4。
我们不需要完全合并,只需要从两个数组中各取一部分,组成前 mid 项元素:
- 从
nums1取前 2 个元素[1, 3] - 从
nums2取前 2 个元素[2, 4] - 组成前 mid 项:
[1, 2, 3, 4]
奇数情况:前 mid 项的最大值就是中位数,即 max(1, 2, 3, 4) = 4。
偶数情况:前 mid 项的最大值 + 后 (n+m-mid) 项的最小值,相加除以 2。
核心思路:找到正确的切割位置,让前 mid 项元素符合要求(即前 mid 项的所有元素都小于后 (n+m-mid) 项的所有元素)。
4. 切割的概念
在数组 nums1 上切一刀(位置 cut1),把 nums1 分成左右两部分。在 nums2 上也切一刀(位置 cut2),保证左边总共有 mid 个元素,即 cut1 + cut2 = mid。
1 | nums1: [1, 3 | 5, 7] |
5. 怎么判断切割正确?
切割后,左半部分包含 nums1 的前 cut1 个和 nums2 的前 cut2 个,右半部分包含 nums1 的后 m-cut1 个和 nums2 的后 n-cut2 个。
要保证左半部分所有元素都小于右半部分,只需要:
nums1左边最大值 <=nums2右边最小值(nums1[cut1-1] <= nums2[cut2])nums2左边最大值 <=nums1右边最小值(nums2[cut2-1] <= nums1[cut1])
因为 nums1 左边本身是递增的,nums2 左边也是递增的,所以只需要判断边界。
6. 怎么找到正确的切割位置?
对切割位置 cut1 使用二分查找!这就是 O(log n) 的来源。
在 nums1 上二分切割位置 cut1,对于每个 cut1,计算 cut2 = mid - cut1,然后判断切割是否正确。如果不正确,根据判断结果调整 cut1 的位置。
思路概览
1 | public double findMedianSortedArrays(int[] nums1, int[] nums2) { |
思路简要说明:
- 保证 nums1 是较短数组:这样在 nums1 上二分,避免 nums2 切割位置越界
- 切割位置:在 nums1 上切 cut1 刀,在 nums2 上切 cut2 = mid - cut1 刀,保证左边总共有 half 个元素
- 判断条件:
aLeft <= bRight且bLeft <= aRight - 二分调整:如果
aLeft > bRight,说明 A 数组切割线位置偏大,左移;否则右移
三、思路详解
第一步:为什么不需要合并数组
以 A = [1, 3, 5, 7] 和 B = [2, 4, 6] 为例:
合并后:[1, 2, 3, 4, 5, 6, 7],中位数是第 4 个元素 4。
但我们只需要从 A 取前 2 个 [1, 3],从 B 取前 2 个 [2, 4],组成左半部分 [1, 2, 3, 4],最大值就是 4。
关键:不需要完全合并,只需要找到正确的切割位置,让左半部分包含 mid 个元素,且左半部分的所有元素都小于右半部分。
第二步:切割的概念
在数组 A 上切一刀,位置 cut1 表示 A 的前 cut1 个元素属于左半部分,剩下的属于右半部分。
比如 A = [1, 3, 5, 7],cut1 = 2 表示:
- 左半部分:
[1, 3] - 右半部分:
[5, 7]
同理,在 B 上切 cut2 = mid - cut1 刀。
第三步:为什么要在较短数组上切
假设 A 长度 4,B 长度 3,mid = (4+3+1)/2 = 4。
如果在 A 上切 cut1 = 0,那么 B 需要切 cut2 = 4,但 B 长度只有 3,越界了。
所以要在较短数组上切,保证 cut2 不会越界。
第四步:判断切割是否正确
切割后,左半部分包含:
- A 的前 cut1 个元素:
A[0...cut1-1] - B 的前 cut2 个元素:
B[0...cut2-1]
右半部分包含:
- A 的后 m-cut1 个元素:
A[cut1...m-1] - B 的后 n-cut2 个元素:
B[cut2...n-1]
要保证左半部分所有元素都小于右半部分,只需要:
A[cut1-1] <= B[cut2](A 左边最大值 <= B 右边最小值)B[cut2-1] <= A[cut1](B 左边最大值 <= A 右边最小值)
因为 A 左边本身是递增的,B 左边也是递增的,所以只需要判断边界。
第五步:二分调整
如果 A[cut1-1] > B[cut2],说明 A 左边太大,需要左移切割位置(减小 cut1)。
否则,需要右移切割位置(增大 cut1)。
完整执行过程(图解)
以 A = [1, 3, 5, 7] 和 B = [2, 4, 6] 为例:
1 | m = 4, n = 3, mid = (4+3+1)/2 = 4 |
再举一个偶数个元素的例子,A = [1, 2] 和 B = [3, 4]:
1 | m = 2, n = 2, mid = (2+2+1)/2 = 2 |
四、总结
寻找两个正序数组的中位数的核心思路:
- 不需要合并数组:只需要找到正确的切割位置
- 在较短数组上二分:避免越界
- 切割位置 cut1 和 cut2:保证左边总共有 mid 个元素
- 判断条件:
A[cut1-1] <= B[cut2]且B[cut2-1] <= A[cut1] - 二分调整:根据判断结果左移或右移
时间复杂度 O(log(min(m, n))),空间复杂度 O(1)。

