开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第24天,点击查看活动详情
题目描述
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
提示:
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-106 <= nums1[i], nums2[i] <= 106
废话不多说,还是先自己做了一遍,我是把两个数组里面的数边比较大小边插入到新数组里面,然后看其长度是奇数还是偶数来获得中位数。思路很简单,解法很粗暴。其实刚开始我不是这么想的,我想先把两个数组直接前后拼接合并,然后冒泡排序,但是那样更麻烦
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int []nums=new int[nums1.length+nums2.length];
for(int i=0;i<nums1.length;i++){
nums[i]=nums1[i];
}
for(int i=0;i<nums2.length;i++){
nums[i+nums1.length]=nums2[i];
}
int n=nums.length;
int count=0,i=0,j=0;
while(count<n){
if(i==nums1.length){
while(j!=nums2.length){
nums[count++]=nums2[j++];
}
break;
}
if(j==nums2.length){
while(i!=nums1.length){
nums[count++]=nums1[i++];
}
break;
}
if(nums1[i]<nums2[j]){
nums[count++]=nums1[i++];
}else{
nums[count++]=nums2[j++];
}
}
if(n%2==0){
return (double)(nums[n/2-1]+nums[n/2])/2;
}else{
return nums[n/2];
}
}
}
但是题目要求的时间复杂度是O(log(m+n),显然暴力解法是无法满足的,那么我们就要想更巧妙的方法。显然,看到log,自然而然就能想到二分查找。
那么我们的思路就可以是这样的,我们不用合并数组,像刚才最后的思路一样,我们要先明确要找的是哪个数,如果总长度是奇数,就找第K小和第k+1小的数,总长为偶数则找第k小的数,此处k=nums.length/2。假设刚开始找第7小的数,我们就比较两个数组第三个数(7/2向下取整),假如nums1的小于nums2的,就把nums1的前三个都排除掉了,那么他们就是前三小的数,接下来我们只需要在剩余的数中找第四小(7-3=4)的数就可以了,那就比较两个数组第二个数,假如nums2的小于nums1的,那么nums2前两个数就被排除了,接下来在剩下的数里找第2小的数(4-2=2),也就是比较第一个,以此类推。
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int n = nums1.length;
int m = nums2.length;
int left = (n + m + 1) / 2;
int right = (n + m + 2) / 2;
//将偶数和奇数的情况合并,如果是奇数,会求两次同样的 k 。
return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, left) + getKth(nums1, 0, n - 1, nums2, 0, m - 1, right)) * 0.5;
}
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;
//让 len1 的长度小于 len2,这样就能保证如果有数组空了,一定是 len1
if (len1 > len2) return getKth(nums2, start2, end2, nums1, start1, end1, k);
if (len1 == 0) return nums2[start2 + k - 1];
if (k == 1) return Math.min(nums1[start1], nums2[start2]);
int i = start1 + Math.min(len1, k / 2) - 1;
int j = start2 + Math.min(len2, k / 2) - 1;
if (nums1[i] > nums2[j]) {
return getKth(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1));
}
else {
return getKth(nums1, i + 1, end1, nums2, start2, end2, k - (i - start1 + 1));
}
}
后来看了题解,发现还有一种很巧妙的方法,我们只需要将数组进行切。
一个长度为 m 的数组,有 0 到 m 总共 m + 1 个位置可以切。我们把数组 A 和数组 B 分别在 i 和 j 进行切割。将 i 的左边和 j 的左边组合成「左半部分」,将 i 的右边和 j 的右边组合成「右半部分」。
- 当 A 数组和 B 数组的总长度是偶数时,如果我们能够保证
*左半部分的长度等于右半部分
i + j = m - i + n - j , 也就是 j = ( m + n ) / 2 - i
*左半部分最大的值小于等于右半部分最小的值 max ( A [ i - 1 ] , B [ j - 1 ])) <= min ( A [ i ] , B [ j ]))
那么,中位数就可以表示如下
(左半部分最大值 + 右半部分最小值 )/ 2。
(max ( A [ i - 1 ] , B [ j - 1 ])+ min ( A [ i ] , B [ j ])) / 2
- 当 A 数组和 B 数组的总长度是奇数时,如果我们能够保证
*左半部分的长度比右半部分大 1
i + j = m - i + n - j + 1也就是 j = ( m + n + 1) / 2 - i
*左半部分最大的值小于等于右半部分最小的值 max ( A [ i - 1 ] , B [ j - 1 ])) <= min ( A [ i ] , B [ j ]))
那么,中位数就是 左半部分最大值,也就是左半部比右半部分多出的那一个数。 max ( A [ i - 1 ] , B [ j - 1 ])
想了一圈,发现这个方法是最巧妙的,写起来也不费劲,这样相比第二个方法反倒复杂了很多。