题目
题解
直接找中位数位置
思路
最直接的想法是合并两个数组,找到中间位置的数;
更进一步,我们其实不需要合并数组,只需要找到中间位置的数即可,则可以借鉴归并排序的思路,利用两个指针,根据情况不断向后移动,直到到达中位数的位置。
思路很好想,但是对于奇数偶数的讨论比较复杂,即这个中位数到底怎么表示?
算法
看着挺简单的代码,实际上内容还挺丰富的,要是我自己写估计得啰里啰嗦写一大堆,精选题解写的非常的精炼
对于len是奇数的情况,中位数出现在下标为len/2的位置;
len是偶数的情况,中位数在下标为len/2-1和len/2之间,
因此,总结下来,不论奇偶,都要经过len/2+1个数才能找到中位数,选择用循环k找到下标为len/2的数。除此之外,为了记录偶数情况中len/2下标的前一个数,设置pre变量记录该数。
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m=nums1.length,n=nums2.length,len=m+n,i=0,j=0;
int pre=-1,cur=-1;
for(int k=0;k<=len/2;k++){
pre=cur;
if(i<m&&(j>=n||nums1[i]<nums2[j]))
cur=nums1[i++];
else
cur=nums2[j++];
}
//偶数
if(len%2==0)
return (double)(pre+cur)/2;
//奇数
return cur;
}
}
时间复杂度: O ( m + n ) O(m+n) O(m+n)
空间复杂度: O ( 1 ) O(1) O(1)
二分查找
单论题来说还算不上困难,难的是如何实现时间复杂度为O(log(m+n))?这个log就让人想到二分,问题可以转化成寻找两个有序数组中的第k小的数,其中k为len/2或者len/2+1
假设两个有序数组分别是 A 和 B。要找到第 k 个元素,我们可以比较 A[k/2−1] 和 B[k/2−1],根据比较结果分为以下这三种情况:
- A[k/2−1]<B[k/2−1],则A[k/2−1]前至多k-2个元素比它小,肯定不是第k小,因此排除A[0]到A[k/2−1]
- A[k/2−1]>B[k/2−1],排除B[0]到B[k/2−1]
- A[k/2−1]==B[k/2−1],可以归入第一种情况处理
可以看到,比较 A[k/2−1] 和 B[k/2−1] 之后,可以排除 k/2 个不可能是第 k 小的数,查找范围缩小了一半。同时,我们将在排除后的新数组上继续进行二分查找,并且根据我们排除数的个数,减少 k 的值,这是因为我们排除的数都不大于第 k 小的数。
这里有三个特殊情况需要单独讨论:
- A[k/2−1] 或 B[k/2−1]越界,那么可以选取对应数组中的最后一个元素。根据排除数的个数减少k的值,而不能直接将k减去k/2。
- 一个数组为空,直接返回另一个数组中的第k小的元素。
- k=1,返回两个数组首元素的最小值。
举个例子,过程如下:
首先讨论len=m+n的奇偶性,令mid=len/2,若len是奇数,k就是mid+1;若是偶数,k为第mid数和第(mid+1)数的1/2。
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m=nums1.length,n=nums2.length,len=m+n,mid=len/2;
if(len%2==1)
return (double)getKth(nums1,nums2,mid+1);
else
return (getKth(nums1,nums2,mid)+getKth(nums1,nums2,mid+1))/2.0;
}
//获得两个数组中第k小的元素
public int getKth(int[] nums1,int[] nums2,int k){
int i=0,j=0,m=nums1.length,n=nums2.length;
while(true){
//特殊情况
if(i==m)
return nums2[j+k-1];//nums1元素都被排除,返回nums2的中位数
if(j==n)
return nums1[i+k-1];
if(k==1)
return Math.min(nums1[i],nums2[j]);
//一般情况
int newI=Math.min(i+k/2,m)-1,newJ=Math.min(j+k/2,n)-1;//避免越界,所以取了i+k/2和m中的最小值
if(nums1[newI]<=nums2[newJ]){
k-=(newI-i+1);//如果i+k/2没有越界,其实就是减去了k/2;如果越界,则是减去了排除数的个数
i=newI+1;//排除了newI前的所有元素
}
else{
k-=(newJ-j+1);
j=newJ+1;
}
}
}
}
时间复杂度: O ( l o g ( m + n ) ) O(log(m+n)) O(log(m+n))
空间复杂度: O ( 1 ) O(1) O(1)
p.s 做了二分查找的解法才觉得,困难题不愧是困难题,真是块难啃的硬骨头