每日一道 LeetCode (47):寻找两个正序数组的中位数
每天 3 分钟,走上算法的逆袭之路。
前文合集
代码仓库
GitHub: https://github.com/meteor1993/LeetCode
Gitee: https://gitee.com/inwsy/LeetCode
题目:寻找两个正序数组的中位数
难度:困难
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。
请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
解题方案一:暴力法
果然是困难难度的题,这道题最简单的暴力法我都调了半天程序才调好。
暴力法的思路很简单,两个有序数组,直接拼起来组合成一个大的有序数组,然后在这个有序数组里面取中位数。
思路很简单,不过代码还是稍微有点不是那么好写的,还好给的是有序数组,如果是无序数组,合并的难度更大。
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int n1 = nums1.length;
int n2 = nums2.length;
int[] nums = new int[n1 + n2];
// 先处理极限情况, nums1 为空
if (n1 == 0) {
// 如果 nums2 长度是偶数
if (n2 % 2 == 0) {
return (nums2[n2 / 2 - 1] + nums2[n2 / 2]) / 2.0;
} else {
return nums2[n2 / 2];
}
}
// 同上,如果 nums2 为空
if (n2 == 0) {
if (n1 % 2 == 0) {
return (nums1[n1 / 2 - 1] + nums1[n1 / 2]) / 2.0;
} else {
return nums1[n1 / 2];
}
}
// 定义新数组的指针
int count = 0;
int i = 0, j = 0;
while (count != (n1 + n2)) {
// 当 nums1 循环结束而 nums2 还有的时候
if (i == n1) {
while (j != n2){
nums[count++] = nums2[j++];
}
break;
}
// 当 nums2 循环结束而 nums1 还有的时候
if (j == n2) {
while (i != n1) {
nums[count++] = nums1[i++];
}
break;
}
if (nums1[i] < nums2[j]) {
nums[count++] = nums1[i++];
} else {
nums[count++] = nums2[j++];
}
}
if (count % 2 == 0) {
return (nums[count / 2 - 1] + nums[count / 2]) / 2.0;
} else {
return nums[count / 2];
}
}
好了,我这个智商基本上对一道困难难度的题能想到这里应该已经算不错的了,剩下的就开始翻答案了。
解题方案二:二分法
感觉答案上给这个方案叫二分法并不是很合适。
这个方案的思路是遮掩的,两个数组 nums1 和 nums2 的长度已经确定了,那么中位数的的位置肯定是 (nums1.length + nums2.length) / 2
,假设长度正好是奇数哈,如果定义刚才那个位置是 k 的话,这个问题就可以成功的转化成另一个问题,如何在两个有序数组中寻找第 k 小的数字。
可以定义分别为两个数组定义两个指针,分别指向两个数组开头的位置,然后移动两个指针,直到第 k 次移动,那么我们就找到了第 k 小的数字。这种方案相当于还是要循环两个数组,接着想办法减少循环次数。
那么就不能是使用循环了,这里就用到了 「二分法」 。
- 两个数组 nums1 和 nums2 ,先取 nums1[k/2 - 1] 记为 pivot1 和 nums2[k/2 - 1] 记为 pivot2 。
- 接着取 pivot = min(pivot1, pivot2) ,那么这时两个数组中小于等于 pivot 的最多不会超过 k - 2 个。
- 这时 pivot 最大也可能是第 k-1 小的元素。
- 如果 pivot = pivot1 ,那么在 nums1 中,从 0 到 k / 2 - 1 都不会是第 k 个元素,把这些元素全部 "删除",剩下的作为新的 nums1 数组。
- 如果 pivot = pivot2 ,那么在 nums2 中,从 0 到 k / 2 - 1 都不会是第 k 个元素,把这些元素全部 "删除",剩下的作为新的 nums2 数组。
- 由于前面删除了一些元素,这时我们需要修改 k 的值,减去我们删掉的元素。
- 重复上面的过程,就可以找到第 k 个元素了。
public double findMedianSortedArrays_1(int[] nums1, int[] nums2) {
int l1 = nums1.length, l2 = nums2.length;
int totalLength = l1 + l2;
if (totalLength % 2 == 1) {
int midIndex = totalLength / 2;
return getKthElement(nums1, nums2, midIndex + 1);
} else {
int midIndex1 = totalLength / 2 - 1, midIndex2 = totalLength / 2;
return (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0;
}
}
// 获取第 k 小的数字
private int getKthElement(int[] nums1, int[] nums2, int k) {
int length1 = nums1.length, length2 = nums2.length;
int index1 = 0, index2 = 0;
while (true) {
// 边界情况
if (index1 == length1) {
return nums2[index2 + k - 1];
}
if (index2 == length2) {
return nums1[index1 + k - 1];
}
if (k == 1) {
return Math.min(nums1[index1], nums2[index2]);
}
// 正常情况
int half = k / 2;
int newIndex1 = Math.min(index1 + half, length1) - 1;
int newIndex2 = Math.min(index2 + half, length2) - 1;
int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2];
if (pivot1 <= pivot2) {
k -= (newIndex1 - index1 + 1);
index1 = newIndex1 + 1;
} else {
k -= (newIndex2 - index2 + 1);
index2 = newIndex2 + 1;
}
}
}
果然不愧是困难模式,上面这段操作我看了好几遍,还自己徒手推导了好几次,才理解答案中的含义,建议各位看不大懂的小伙伴自己在纸上画两个数组,徒手推导一次,绝对事半功倍。
作者:极客挖掘机
定期发表作者的思考:技术、产品、运营、自我提升等。
本文版权归作者极客挖掘机和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
如果您觉得作者的文章对您有帮助,就来作者个人小站逛逛吧:极客挖掘机