这是LeetCode上第一个"困难"题目. 自己求解时, 走进个误区, 一直不能求解.
于是网搜了一些解法, 挺多看起来是对的, 但实际跑几个case就发现错误了.
后来找到这篇正确解法. 分析也很全面, 再补充一些细节在这里.
列一下关键点:
- 使用二分法. 求正序数组长度为len的中位数, 就是求这个数组第k小的数, k=(len+1)/2, 假设len为奇数.
- 求的是合并后的中位数, 而不是每个裁剪后数组的中位数. 我自己的误区就在这里.
- 求第k小的值, 这个k的值在数组每次二分裁剪后, 是会变化的. 因为裁剪后, 整体数组长度变了. k的变化方式参看代码注释.
- 长度为len的数组, 取中位数的index的公式 (len+1)/2 和 (len+2)/2
可以在稿纸上划出合并后的正序大数组, 方便推演二分进程. 以此来应对脑力不足, 或者数学力弱的情况.
不知道有多久没做这类题了, 很多细节都退步了, 细细的做吧, 先不图快.
感觉这个用时1ms不太正常, 没准是新人刷题鼓励吧..
直接上源码吧, 在原来的基础上补充了更完整的注释.
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int n = nums1.length;
int m = nums2.length;
// 求中值所在的index, 两个数组总长度为偶数时有两个值, 左和右. 奇数时只有一个, 或者说左和右是同一个index.
int left = (n + m + 1) / 2;
int right = (n + m + 2) / 2;
// 将偶数和奇数的情况合并,如果是奇数,会求两次同样的 k 。
// 奇数时可以优化, 单独做个if处理, 减少运算次数
return (
getKth(nums1, 0, n - 1, nums2, 0, m - 1, left) +
getKth(nums1, 0, n - 1, nums2, 0, m - 1, right)
) * 0.5; //这里 乘以0.5 也可以是除以2f, 但不能是除以2.
}
/**
* 找到两个数组里第k小的值.
*/
private int getKth(int[] nums1, int start1, int end1, int[] nums2, int start2, int end2, int k) {
int len1 = end1 - start1 + 1;
int len2 = end2 - start2 + 1;
if (len1 > len2){
//让 len1 的长度小于 len2,这样就能保证如果有数组空了,一定是 len1
return getKth(nums2, start2, end2, nums1, start1, end1, k);
}
// 这两个if是递归的终点, 最终的值从这里返回
if (len1 == 0){
// 数组1的长度为0, 取数组2的第k小值即可
return nums2[start2 + k - 1];
}
// 第1小值, 直接比较两个数组起始点所在的值, 并返回最小值就可以了. 不用再裁剪数组了
if (k == 1){
// 因为是求第k"小"值, 所以这里用min().
return Math.min(nums1[start1], nums2[start2]);
}
// 递归准备:
// 两个数组, 分别取第k/2小的值, 作为分界线. 注意这里求得是数组index的值, 需要-1
int halfOfk1 = start1 + Math.min(len1, k / 2) - 1;
int halfOfk2 = start2 + Math.min(len2, k / 2) - 1;
if (nums1[halfOfk1] <= nums2[halfOfk2]) {
// 分支 数组1第(k/2)小的值 小于等于 数组2第(k/2)的值.
/**
* 我们假设两个数组合并形成了一个"正序大数组".
* 二分法并不会真的实现这个数组, 所以我们假设这个大数组在我们的草稿纸上.
*
* 因为在正序数组里, 任意第(k/2)小的值肯定小于等于第(k)小的值.
* 同时在当前if分支里, 数组1第(k/2)小的值又 小于等于 数组2第(k/2)的值,
* 说明 正序数组1[k/2]的左边, 肯定不包含"大数组"中第k小的值,
* 所以可以抛弃 数组1(k/2)左边的所有数据. 进入递归
*/
return getKth(nums1,
// 抛弃操作, 将(halfOfk1 + 1)作为数组起始值, 也就是抛弃了halfOfk1左边的数据
halfOfk1 + 1,
end1,
nums2, start2, end2,
/**
* k值更新操作:
* (halfOfk1+1 - start1)是大数组左边被抛弃的长度,
* 如果大数组"第k小值"的左边被减掉x个值, 形成一个新的短数组,
* 那么原本求的"第k小值", 在这个新数组里, 就变成"第(k-x)小值"了.
*/
k - (halfOfk1 + 1 - start1)
);
} else {
//数组1的分界值 比 数组2的分界值大, 同理, 抛弃数组2分界值的左边.
return getKth(nums1, start1, end1, nums2, halfOfk2 + 1, end2, k - (halfOfk2 - start2 + 1));
}
}