1. 问题背景与核心挑战
这道LeetCode经典难题要求在两个已排序的正序数组中找出合并后的中位数。表面看是简单的中位数计算,实则考察对二分查找算法的深度理解和灵活运用能力。我在大厂面试中多次遇到该题的变种,发现90%的候选人都会卡在最优解的实现上。
中位数计算的本质是找到第k小元素。对于总长度为偶数的数组,中位数是中间两个数的平均值;奇数长度则是中间那个数。直接合并再取中位数的解法时间复杂度为O(m+n),无法通过LeetCode的严格测试用例。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 暴力解法:合并后取中位数
2.1 基础实现步骤
cpp复制double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
vector<int> merged;
merge(nums1.begin(), nums1.end(),
nums2.begin(), nums2.end(),
back_inserter(merged));
int n = merged.size();
return n % 2 ? merged[n/2]
: (merged[n/2-1] + merged[n/2])/2.0;
}
这种方法直接调用STL的merge算法,虽然代码简洁,但需要额外O(m+n)空间,且时间复杂度过高。
2.2 空间优化版本
可以通过双指针法避免显式合并:
cpp复制double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size(), n = nums2.size();
int i = 0, j = 0, count = 0;
int median1 = 0, median2 = 0;
while (count <= (m + n)/2) {
median1 = median2;
if (i < m && (j >= n || nums1[i] < nums2[j])) {
median2 = nums1[i++];
} else {
median2 = nums2[j++];
}
count++;
}
return (m + n) % 2 ? median2 : (median1 + median2) / 2.0;
}
这个版本空间复杂度降为O(1),但时间复杂度仍是O(m+n)。
3. 最优解:二分查找法
3.1 算法核心思想
通过划分两个数组,使得:
- 左半部分元素数量等于右半部分(或左半多1个)
- 左半部分所有元素 ≤ 右半部分所有元素
关键公式:
cpp复制i + j = (m + n + 1) / 2
nums1[i-1] <= nums2[j] && nums2[j-1] <= nums1[i]
3.2 完整实现代码
cpp复制double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
if (nums1.size() > nums2.size()) {
return findMedianSortedArrays(nums2, nums1);
}
int m = nums1.size(), n = nums2.size();
int left = 0, right = m;
int total_left = (m + n + 1) / 2;
while (left <= right) {
int i = left + (right - left) / 2;
int j = total_left - i;
int nums1_left = (i == 0) ? INT_MIN : nums1[i-1];
int nums1_right = (i == m) ? INT_MAX : nums1[i];
int nums2_left = (j == 0) ? INT_MIN : nums2[j-1];
int nums2_right = (j == n) ? INT_MAX : nums2[j];
if (nums1_left <= nums2_right && nums2_left <= nums1_right) {
if ((m + n) % 2 == 1) {
return max(nums1_left, nums2_left);
} else {
return (max(nums1_left, nums2_left) + min(nums1_right, nums2_right)) / 2.0;
}
} else if (nums1_left > nums2_right) {
right = i - 1;
} else {
left = i + 1;
}
}
return 0.0;
}
3.3 关键点解析
- 边界处理:当i=0或j=0时,对应数组左半部分为空;当i=m或j=n时,右半部分为空
- 循环条件:left <= right确保搜索空间有效
- 中位数计算:根据总长度奇偶性分别处理
- 指针移动:通过比较nums1[i-1]和nums2[j]决定搜索方向
4. 复杂度分析与对比
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 合并后取中位数 | O(m+n) | O(m+n) | 教学演示 |
| 双指针法 | O(m+n) | O(1) | 数据量较小 |
| 二分查找法 | O(log(min(m,n))) | O(1) | 面试、竞赛和大数据场景 |
实测在LeetCode上,二分查找法的运行时间比暴力解法快10倍以上(4ms vs 48ms)。
5. 常见错误与调试技巧
5.1 典型错误案例
- 数组越界:未正确处理i=0或j=0的边界情况
- 死循环:二分查找终止条件设置不当
- 整数溢出:使用(left+right)/2而非left+(right-left)/2
- 类型错误:忘记将结果转换为double导致精度丢失
5.2 调试建议
- 打印每次循环的i,j值及四个边界值
- 使用小规模测试用例(如[1,3]和[2])逐步验证
- 特别注意m=0或n=0的特殊情况
- 检查奇数长度和偶数长度的不同处理逻辑
6. 算法扩展与变种
6.1 寻找第k小元素
将中位数问题泛化为寻找两个有序数组的第k小元素:
cpp复制int getKthElement(const vector<int>& nums1, const vector<int>& nums2, int k) {
int m = nums1.size(), n = nums2.size();
int index1 = 0, index2 = 0;
while (true) {
if (index1 == m) return nums2[index2 + k - 1];
if (index2 == n) return nums1[index1 + k - 1];
if (k == 1) return min(nums1[index1], nums2[index2]);
int newIndex1 = min(index1 + k/2 - 1, m-1);
int newIndex2 = min(index2 + k/2 - 1, n-1);
if (nums1[newIndex1] <= nums2[newIndex2]) {
k -= newIndex1 - index1 + 1;
index1 = newIndex1 + 1;
} else {
k -= newIndex2 - index2 + 1;
index2 = newIndex2 + 1;
}
}
}
6.2 多数组情况
对于k个排序数组,可以使用最小堆维护每个数组的当前指针,时间复杂度O(klogk)
7. 面试实战技巧
-
沟通策略:先阐述暴力解法,再逐步优化
-
白板编码:先写出框架再填充细节
-
测试用例:必须包含以下场景:
- 一个数组为空
- 两个数组长度相等
- 总长度为奇数和偶数
- 所有元素在其中一个数组中
-
优化思路:强调log(min(m,n))复杂度的推导过程
-
问题延伸:准备讨论如何扩展到k个数组的情况
8. 性能优化进阶
对于特别大的数据集(如GB级别):
- 使用内存映射文件处理无法全部加载到内存的数组
- 考虑使用多线程并行处理两个数组的二分查找
- 对于数据分布均匀的情况,可以先采样估计中位数大致范围
9. 其他语言实现要点
9.1 Python版本
python复制def findMedianSortedArrays(nums1, nums2):
if len(nums1) > len(nums2):
nums1, nums2 = nums2, nums1
m, n = len(nums1), len(nums2)
left, right = 0, m
total_left = (m + n + 1) // 2
while left <= right:
i = (left + right) // 2
j = total_left - i
nums1_left = float('-inf') if i == 0 else nums1[i-1]
nums1_right = float('inf') if i == m else nums1[i]
nums2_left = float('-inf') if j == 0 else nums2[j-1]
nums2_right = float('inf') if j == n else nums2[j]
if nums1_left <= nums2_right and nums2_left <= nums1_right:
if (m + n) % 2 == 1:
return max(nums1_left, nums2_left)
else:
return (max(nums1_left, nums2_left) + min(nums1_right, nums2_right)) / 2
elif nums1_left > nums2_right:
right = i - 1
else:
left = i + 1
return 0.0
9.2 Java版本
注意整数除法与浮点数转换的处理差异
10. 学习资源推荐
- 《算法导论》第9章 中位数和顺序统计量
- LeetCode官方题解视频
- MIT 6.006 Introduction to Algorithms讲座
- 可视化调试工具:https://www.cs.usfca.edu/~galles/visualization/Median.html
在实际面试中,我曾遇到一个变种问题:如何在10亿个有序整数中快速找到中位数?这时就需要结合外排序和本文的二分查找思想,设计分布式解决方案。这提醒我们,算法问题的解决往往需要根据数据规模灵活调整策略。
