4. 寻找两个正序数组的中位数
问题描述
给定两个大小分别为 m 和 n 的正序(非递减)整数数组 nums1 和 nums2,找出并返回这两个正序数组的中位数。要求算法的时间复杂度为 O(log(m+n))。
示例:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3],中位数 2
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4],中位数 (2+3)/2 = 2.5
算法思路
二分查找法:
- 核心思想:
将寻找中位数转化为寻找第k小的数(k为中间位置)。对于两个有序数组,可以通过比较第k/2个元素,每次排除k/2个不可能的元素。 - 关键步骤:
- 若总长度
m+n为奇数,中位数是第(m+n+1)/2小的数 - 若为偶数,中位数是第
(m+n)/2和(m+n)/2+1小的数的平均值
- 若总长度
- 查找第
k小的数:- 比较
nums1[k/2-1]和nums2[k/2-1] - 若
nums1[k/2-1] < nums2[k/2-1],排除nums1的前k/2个元素 - 反之排除
nums2的前k/2个元素 - 递归处理剩余元素,更新
k = k - k/2
- 比较
- 边界处理:
- 若一个数组为空,直接在另一个数组找第
k小的数 - 若
k=1,返回两个数组首元素的较小值 - 若数组长度不足
k/2,取末尾元素比较
- 若一个数组为空,直接在另一个数组找第
代码实现
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int total = nums1.length + nums2.length;
// 奇数:返回第 (total+1)/2 小的数
if (total % 2 == 1) {
return findKthElement(nums1, nums2, (total + 1) / 2);
}
// 偶数:返回第 total/2 和 total/2+1 小的数的平均值
else {
double left = findKthElement(nums1, nums2, total / 2);
double right = findKthElement(nums1, nums2, total / 2 + 1);
return (left + right) / 2.0;
}
}
/**
* 在两个有序数组中查找第 k 小的元素
*
* @param nums1 第一个有序数组
* @param nums2 第二个有序数组
* @param k 目标位置
* @return 第 k 小的元素值
*/
private double findKthElement(int[] nums1, int[] nums2, int k) {
int index1 = 0, index2 = 0; // 当前查找的起始位置
int len1 = nums1.length, len2 = nums2.length;
while (true) {
// 边界处理:一个数组已全部排除
if (index1 == len1) return nums2[index2 + k - 1];
if (index2 == len2) return nums1[index1 + k - 1];
if (k == 1) return Math.min(nums1[index1], nums2[index2]); // k=1时直接返回最小值
// 计算比较位置(处理数组长度不足的情况)
int half = k / 2;
int newIndex1 = Math.min(index1 + half, len1) - 1;
int newIndex2 = Math.min(index2 + half, len2) - 1;
// 比较并排除较小元素所在数组的前半部分
if (nums1[newIndex1] <= nums2[newIndex2]) {
k -= (newIndex1 - index1 + 1); // 更新剩余 k 值
index1 = newIndex1 + 1; // 更新数组1的起始位置
} else {
k -= (newIndex2 - index2 + 1);
index2 = newIndex2 + 1;
}
}
}
}
代码注释
| 代码部分 | 说明 |
|---|---|
total % 2 == 1 | 判断总长度是否为奇数 |
findKthElement(...) | 查找第 k 小的元素 |
index1, index2 | 当前查找的起始位置 |
k == 1 处理 | 返回两数组当前首元素的最小值 |
half = k / 2 | 每次排除的元素数量 |
Math.min(index+half, len)-1 | 防止数组越界 |
k -= (newIndex - index + 1) | 更新剩余查找长度 |
index = newIndex + 1 | 更新数组起始位置 |
算法过程
nums1 = [1,3], nums2 = [2] :
- 计算中位数位置:
- 总长度
3(奇数),k = (3+1)/2 = 2
- 总长度
- 查找第 2 小的数:
index1=0,index2=0,k=2half=1→newIndex1=min(0+1,2)-1=0→nums1[0]=1newIndex2=min(0+1,1)-1=0→nums2[0]=21 < 2→ 排除nums1[0],k=2-1=1,index1=1k=1→ 返回min(nums1[1], nums2[0])=min(3,2)=2
复杂度分析
- 时间复杂度:O(log(m+n))
每次循环排除约k/2个元素,k初始值最大为(m+n)/2,故时间复杂度为对数级 - 空间复杂度:O(1)
仅使用常数空间
关键点
- 问题转化:
将中位数问题转化为寻找第k小元素问题 - 二分排除:
通过比较第k/2个元素,每次排除k/2个不可能的元素 - 边界处理:
- 数组长度不足时取末尾元素
k=1时直接返回最小值- 一个数组空时处理另一数组
- 高效性:
利用有序数组特性,避免完全合并数组
测试用例
public static void main(String[] args) {
Solution solution = new Solution();
// 示例测试
int[] nums1 = {1, 3};
int[] nums2 = {2};
System.out.println(solution.findMedianSortedArrays(nums1, nums2)); // 2.0
int[] nums3 = {1, 2};
int[] nums4 = {3, 4};
System.out.println(solution.findMedianSortedArrays(nums3, nums4)); // 2.5
// 一个数组为空
int[] nums5 = {};
int[] nums6 = {1};
System.out.println(solution.findMedianSortedArrays(nums5, nums6)); // 1.0
// 交叉数组
int[] nums7 = {1, 3, 5};
int[] nums8 = {2, 4, 6};
System.out.println(solution.findMedianSortedArrays(nums7, nums8)); // 3.5
// 大数测试
int[] nums9 = {100000};
int[] nums10 = {100001};
System.out.println(solution.findMedianSortedArrays(nums9, nums10)); // 100000.5
}
常见问题
-
为什么比较
k/2位置的元素?
通过比较两个数组的第k/2个元素,可以确定至少一个数组的前k/2个元素不包含第k小的元素,从而高效排除。 -
如何处理数组长度不足
k/2?
取数组末尾元素进行比较,确保不会越界,且能正确排除有效元素。 -
为什么时间复杂度是 O(log(m+n))?
每次排除约k/2个元素,k值呈指数级减少,故时间复杂度为对数级。 -
能否合并数组后直接求中位数?
合并数组需 O(m+n) 时间,不满足题目要求的 O(log(m+n)) 时间复杂度。
1501

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



