题目:
给定两个正序数组(数值从小到大排列)nums1
和nums2
,求出并返回两个数组的中位数
思路:
总的来说分两种思路:
- 第一种简单易想,就是将两个数组按照正序合并成一个新的数组,之后根据索引返回它的中位数。时间复杂度为
O(m+n)
,也就是遍历两个数组的时间。空间复杂度为O(m+n)
:开辟了一个新的数组
以下为第一种方法代码+注释:比较简单,本文主要说明第二种
方法一:暴力解法:时间复杂度O(m+n)
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 1.先将两数组合并
int[] help = new int[nums1.length + nums2.length];
int p1 = 0, p2 = 0, cur = 0;
while(p1 < nums1.length && p2 < nums2.length){
help[cur++] = nums1[p1] > nums2[p2] ? nums2[p2++] : nums1[p1++];
}
while(p1 < nums1.length){
help[cur++] = nums1[p1++];
}
while(p2 < nums2.length){
help[cur++] = nums2[p2++];
}
int len = nums1.length + nums2.length;
double res = 0.0;
// 如果为奇数个,就返回正中间那个
if((len & 1) == 1){
res = help[len / 2];
}else{
res = (help[len / 2 - 1] + help[len / 2]) / 2.0;
}
// 2.返回中位数
return res;
}
- 第二种:既然是查找中位数,那我们可以使用
二分查找
来解决这道题。也就可以将该题转化为求第k小数。
第二种方法主要思路:
- 先根据数组总长度找到中位数的位置,也就是我们要求的
第k个数
- 分到每个数组中索引值就变成了
k/2-1
(因为索引是从0开始的所以要-1),让当前时刻两个数组中k/2-1
位置的值进行比较,小的那一个数组前面k/2个数一定都不满足,这样一次就砍掉了一半需要查找的数,直到最后k==1
的时候,返回两个数组中当前位置第一个值较小的那一个Math.min(nums1[start2],nums2[start2]
。 - 特殊情况:有可能出现一个数组长度比
k/2
还要小的情况,这时候直接根据索引在另一个数组中返回值即可
该方法时间复杂度为O(log(m+n)),因为每次都减少了一半的查找量,空间复杂度为O(1)
以下为代码+注释,结合注释看应该好理解一点:
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 方法二:二分查找
int len1 = nums1.length;
int len2 = nums2.length;
// 找到左边的值和右边的值
// 将奇数情况和偶数情况合并
// 奇数情况下left和right也指向的同一个值,只不过计算了两遍最后除2即可
int left = (len1 + len2 + 1) / 2;
int right = (len1 + len2 + 2) / 2;
// 都按照偶数情况的算,算出左边的和右边的加起来除2就是最终结果,奇数情况无非重复计算两遍
return (getKthSmall(nums1, 0, len1 - 1, nums2, 0, len2 - 1, left) +
getKthSmall(nums1, 0, len1 - 1, nums2, 0, len2 - 1, right)) / 2;
}
public double getKthSmall(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)
return getKthSmall(nums2, start2, end2, nums1, start1, end1, k);
// 如果第一个数组长度为0,直接返回第二个数组剩下的元素中第k个位置的数
// 注意要-1,因为索引从0开始的
if(len1 == 0)
return nums2[start2 + k - 1];
// 临界情况,只需要找第1小的数,那么就是当前两个数组中首元素最小的那个,取出并返回
if(k == 1)
return Math.min(nums1[start1], nums2[start2]);
// 找出每个数组剩余数组元素中第k/2位置处的值,索引从0开始的,所以最后要-1才对应数组中的位置
int p1 = start1 + Math.min(len1, k / 2) - 1;
int p2 = start2 + Math.min(len2, k / 2) - 1;
// 如果当前位置第一个数组值大于第二个数组,那么就将第二个数组中p2索引之前的值删除
// 因为已经肯定不在你这里面了
// 代码实现上就是改变第二个数组的start2的索引
if(nums1[p1] > nums2[p2]){
return getKthSmall(nums1, start1, end1, nums2, p2 + 1, end2, k - (p2 - start2 + 1));
}else{
return getKthSmall(nums1, p1 + 1, end1, nums2, start2, end2, k - (p1 - start1 + 1));
}
}
笔者也在不断学习中,如有错误,欢迎指正!