【Leetcode hot 100】4.寻找两个正序数组的中位数
问题链接
4.寻找两个正序数组的中位数
问题描述
给定两个大小分别为 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
-10^6 <= nums1[i], nums2[i] <= 10^6
问题解答
核心思路是通过二分查找将问题转化为 “寻找两个有序数组中的第 k 小元素”,再利用中位数的定义(奇数取中间值,偶数取中间两值平均)得到结果。
方法原理
1. 中位数与第 k 小元素的关联
设两个数组长度分别为 m
和 n
,总长度为 total = m + n
:
- 若
total
为奇数:中位数是第(total + 1) / 2
小的元素(如 total=3,取第 2 小)。 - 若
total
为偶数:中位数是第total/2
小和第(total/2)+1
小元素的平均值(如 total=4,取第 2、3 小的平均)。
为简化代码,统一计算第 left = (total + 1) / 2
小和第 right = (total + 2) / 2
小的元素,再求平均——奇数时 left = right
,结果等价于直接取中间值。
2. 二分查找找第 k 小元素
通过二分法每次淘汰两个数组中“不可能包含第 k 小元素”的前 k/2
个元素,逐步缩小搜索范围:
- 边界处理:若某数组已遍历完(起始索引超出长度),直接从另一数组取第
k
小元素。 - k=1 特殊情况:直接返回两个数组当前起始位置的最小值(此时第 1 小即最小值)。
- 二分淘汰:
- 分别取两个数组当前起始位置后
k/2 - 1
索引的值(记为mid1
、mid2
),若数组长度不足则取Integer.MAX_VALUE
(表示该数组无足够元素可淘汰)。 - 若
mid1 < mid2
:说明nums1
的前k/2
个元素不可能是第 k 小,淘汰它们(起始索引后移k/2
),并更新k = k - k/2
。 - 反之:淘汰
nums2
的前k/2
个元素,同理更新k
。
- 分别取两个数组当前起始位置后
- 递归/迭代缩小范围:重复上述步骤,直到
k=1
或某数组遍历完。
完整 Java 代码
class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {int m = nums1.length;int n = nums2.length;int total = m + n;// 计算需要找的第 left 小和第 right 小元素int left = (total + 1) / 2;int right = (total + 2) / 2;// 两元素平均值即为中位数return (findKth(nums1, 0, nums2, 0, left) + findKth(nums1, 0, nums2, 0, right)) / 2.0;}/*** 寻找两个有序数组中的第 k 小元素* @param nums1 第一个有序数组* @param i nums1 的当前起始搜索索引* @param nums2 第二个有序数组* @param j nums2 的当前起始搜索索引* @param k 目标第 k 小元素* @return 第 k 小元素的值*/private int findKth(int[] nums1, int i, int[] nums2, int j, int k) {// 边界1:nums1 已遍历完,直接从 nums2 取第 k 小(j + k - 1 索引)if (i >= nums1.length) {return nums2[j + k - 1];}// 边界2:nums2 已遍历完,直接从 nums1 取第 k 小if (j >= nums2.length) {return nums1[i + k - 1];}// 边界3:k=1,返回当前起始位置的最小值if (k == 1) {return Math.min(nums1[i], nums2[j]);}// 计算当前要比较的中间索引(避免越界,越界则设为最大值)int mid1 = (i + k/2 - 1 < nums1.length) ? nums1[i + k/2 - 1] : Integer.MAX_VALUE;int mid2 = (j + k/2 - 1 < nums2.length) ? nums2[j + k/2 - 1] : Integer.MAX_VALUE;// 二分淘汰:淘汰较小值所在数组的前 k/2 个元素if (mid1 < mid2) {// 淘汰 nums1 的前 k/2 个,更新起始索引和 kreturn findKth(nums1, i + k/2, nums2, j, k - k/2);} else {// 淘汰 nums2 的前 k/2 个,更新起始索引和 kreturn findKth(nums1, i, nums2, j + k/2, k - k/2);}}
}
代码解释
-
主函数
findMedianSortedArrays
:- 计算总长度
total
,确定需要寻找的第left
小和第right
小元素。 - 调用
findKth
方法获取这两个元素,返回它们的平均值(自动处理奇偶数情况)。
- 计算总长度
-
辅助函数
findKth
:- 边界处理:当某数组遍历完(
i >= m
或j >= n
),直接从另一数组取对应位置元素。 - k=1 处理:直接返回两数组当前起始位置的最小值,避免无意义的二分。
- 二分比较:通过
mid1
和mid2
确定淘汰哪部分元素,缩小k
和搜索范围,递归直至找到目标元素。
- 边界处理:当某数组遍历完(
复杂度分析
- 时间复杂度:O(log(m+n))。每次二分操作将
k
缩小一半,总递归/迭代次数为 log(m+n)。 - 空间复杂度:O(log(m+n))。递归调用栈的深度为 log(m+n)(若改为迭代实现,空间复杂度可优化至 O(1))。
测试用例验证
示例 1
- 输入:
nums1 = [1,3]
,nums2 = [2]
- 总长度
total=3
,left=2
,right=2
findKth(nums1,0,nums2,0,2)
过程:- k=2,mid1 = nums1[0] = 1,mid2 = nums2[0] = 2。
- mid1 < mid2,淘汰 nums1 前 1 个元素,k=1,起始索引 i=1。
- 此时 k=1,返回 min(nums1[1]=3, nums2[0]=2) = 2。
- 结果:2.0,符合预期。
示例 2
- 输入:
nums1 = [1,2]
,nums2 = [3,4]
- 总长度
total=4
,left=2
,right=3
findKth(...,2)
返回 2,findKth(...,3)
返回 3,平均值为 2.5,符合预期。