问题描述
给定两个大小为 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
解题思路
看这个时间复杂度,应该是要进行二分求解,题意没有说清,应该是两个有序数组放在一起排序后的中位数。我们可以将其变得更加通用一些,转化为求两个有序数组的第K小数,要求时间复杂度O(log(m+n))。官方题解是二分求中位数,我们这里用递归来讲一下求第K小数。这里用的是二分的思想,但是是递归的模版。
假设第一个数组是a数组,长度是n,第二个数组是b数组,长度是m。假设下标从0开始,设 x = K 2 − 1 x=\frac{K}{2}-1 x=2K−1,我们先从第一个数组中找到第 K 2 \frac{K}{2} 2K个数为 a x a_x ax,第二个数组中找到第 K 2 \frac{K}{2} 2K个数为 b x b_x bx。然后比较这两个数,如果 a x < b x a_x<b_x ax<bx,说明第K个数位于a数组的第 K 2 \frac{K}{2} 2K个数的后半段,或者b数组中。
比如我们考虑两个数组:
a[1, 3, 6, 7]
b[2, 4, 8, 10, 13]
我们要找到的中位数是6,是第5个数, x = 5 2 − 1 x=\frac{5}{2}-1 x=25−1,首先第一步我们找到 a x = 3 , b x = 4 a_x=3, b_x=4 ax=3,bx=4,经过比较,发现 a x < b x a_x<b_x ax<bx,所以我们剩下的数组是(将小的那个a_x也扔掉):
a[6, 7]
b[2, 4, 8, 10, 13]
然后在这个里面搜第 K − K 2 K- \frac{K}{2} K−2K大元素,也就是第 3 3 3大元素,此时 x = 3 2 − 1 x=\frac{3}{2}-1 x=23−1,找到 a x = 6 , b x = 2 a_x=6,b_x=2 ax=6,bx=2,比较发现 a x > b x a_x>b_x ax>bx,所以我们剩下的数组是:
a[6, 7]
b[4, 8, 10, 13]
然后搜第 3 − 3 / 2 = 2 3-3/2=2 3−3/2=2大元素,此时 x = 2 2 − 1 x=\frac{2}{2}-1 x=22−1,找到 a x = 6 , b x = 4 a_x=6, b_x=4 ax=6,bx=4,比较发现 a x > b x a_x>b_x ax>bx,所以还剩下的是:
a[6, 7]
b[8, 10, 13]
此时应该找第1个元素,也就是两个数组首元素中最小的一个。因此最终答案就是6。
那为什么这样削呢?我们看下面这个图:
a和b是原数组,c是合并之后的数组,蓝线表示
a
x
a_x
ax,红线表示
b
x
b_x
bx,那么我们看c中,蓝线前面必定少于K个元素(因为
a
x
<
b
x
a_x<b_x
ax<bx,同时这两个都是第
K
2
\frac{K}{2}
2K个元素,所以二者前面(包含二者)总共有K个元素(如果K为奇数就是K-1个元素)),所以说前面这部分就可以扔掉,直接去除了
K
2
\frac{K}{2}
2K个数据。
还要考虑一种情况,就是你拿不到第 K 2 \frac{K}{2} 2K个元素,就是某个数组太短了。这个时候我们就直接取最短的那个数组的最后一个元素,然后进行比较。如果这个更小,那么就将整个数组去掉,同时K-size()。
由于我们每次都是删除掉 K 2 \frac{K}{2} 2K个元素,而 K = n + m 2 K=\frac{n+m}{2} K=2n+m,所以时间复杂度是O(log(m+n))的。同时空间复杂度也非常优秀,虽然我们用到了递归,但是这个递归属于尾递归,所以编译器不需要不停地堆栈,空间复杂度为 O(1)。
参考博客 https://leetcode.wang
完整代码
class Solution {
public:
int getKth(vector<int>& nums1, int start1, vector<int>& nums2, int start2, int k){
int len1=nums1.size()-start1,len2=nums2.size()-start2;
if(len1==0) return nums2[start2+k-1];//某个数组为空,直接返回另一个数组的第k个元素
if(len2==0) return nums1[start1+k-1];
if(k==1) return min(nums1[start1],nums2[start2]);//找第1个元素,直接找两个数组首个元素的最小值
int k1=start1+min(len1,k/2)-1,k2=start2+min(len2,k/2)-1;//找到两个需要断的地方
return nums1[k1]<nums2[k2] ? getKth(nums1,k1+1,nums2,start2,k-(k1-start1+1)):getKth(nums1,start1,nums2,k2+1,k-(k2-start2+1));
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2){
int n=nums1.size(),m=nums2.size();
if((n+m)%2) return getKth(nums1,0,nums2,0,(n+m+1)/2);//如果是奇数,找到中位数
else return (getKth(nums1,0,nums2,0,(n+m)/2)+getKth(nums1,0,nums2,0,(n+m+2)/2))*0.5;//偶数找到中间两个平均值
}
};