给定两个大小分别为 m 和 n 的 正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数 。算法的时间复杂度应该为 O(log (m+n)) 。
使用归并的方式,合并两个有序数组,得到一个大的有序数组。大的有序数组的中间位置的元素,即为中位数。
不需要合并两个有序数组,只要找到中位数的位置即可。由于两个数组的长度已知,因此中位数对应的两个数组的下标之和也是已知的。维护两个指针,初始时分别指向两个数组的下标 00 的位置,每次将指向较小值的指针后移一位(如果一个指针已经到达数组末尾,则只需要移动另一个数组的指针),直到到达中位数的位置。
上述的两种方法也能解决问题,并且容易想到,但是其时间复杂度不满足要求,这里不再解释和介绍。
分析上面的情形,对于两个数组,我们常规的思路是对两个数组分别设置两个指针,然后不断地移动指向较小数字的指针,指到找到中间位置。这个方法是逐个元素逐个元素的统计,直到找到中间位置。
那么,我们能否找到一种方法,可以一批一批的统计元素,直到找到中间位置呢?答案是可以的。
中位数的特点很清楚,给定一个数组,只要确定中间位置的索引,那么就可以确定中位数。但是现在是两个数组的中位数,不过其思想是相同的,即都是寻找指定位置。(偶数个元素需要寻找两个位置然后求平均值,奇数个元素需要寻找一个位置即可,就是中间位置)。现在我们的问题变成了:给定两个正序数组,寻找第 k 小的元素。
对于这个问题,我们可以分别在两个数组左边获取一个 k 2 − 1 \frac{k}{2}-1 2k−1 长度的子数组,这时会有下面的重要性质,请仔细体会一下:
有两个数组:
[1,2,6,7,8,11,15]
[3,5,6,8,9,10,12,17,19]
假设现在的 k = 10,得到的两个子数组(含有元素 k 2 − 1 \frac{k}{2}-1 2k−1 个)如下:
[1,2,6,7]
[3,5,6,8]
可以看出题目的意思是找到第 10 小的元素。
可以看出两个子数组的最后一个元素 7 < 8,那么对于 7 这个子数组:
[1,2,6,7]
一定是位于前 10 小的元素之中,为什么这么说?请想象一下,第一个数组中比 7 大的元素一定在 7 的右边,第二个数组中比 8 大的元素一定在 8 的右边,当然上述比 7 大的和比 8 大的都没有表示出来。那么谁一定比 7 小?当然是 7 左边的元素以及 7,因为每个数组的长度是 k 2 − 1 \frac{k}{2}-1 2k−1,所以可以肯定含 7 的这个子数组可以移除了,现在的目标变成了从新的两个数组中寻找第 (10 - 4) 小的元素!
上面的过程可以递归进行,不过需要注意终止条件:当目标变成寻找第 1 小的元素时,只需要返回两个数组中第一个元素的最小值即可。
除此之外,还是需要一些问题要考虑,加入有两个数组:
[1]
[2,3,4,5,6,7,8]
目的是寻找第 6 小的元素,显然需要取的子数组的大小是 k 2 − 1 = 2 \frac{k}{2}-1=2 2k−1=2,此时明显超出了第一个数组的维度,这种情况下,将第一个数组取完即可,不必一定要取 k 2 − 1 \frac{k}{2}-1 2k−1 大小,当然此时能够移去的元素数量也要具体情况具体分析了!
到现在,事情还差一点就能完美了,还是上面的例子,在经历一步之后,变为下面的问题,从:
[]
[2,3,4,5,6,7,8]
中找到第 5 小的元素,次数第一个元素已经为空,就不要在取 k 2 − 1 \frac{k}{2}-1 2k−1 子数组了(提醒一下,现在的 k 是 5,不是 6 了!)问题本质上就是从一个正序数组中找到第 5 小的元素,直接返回就好了!
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int len1 = nums1.size(),len2 = nums2.size();
double res1 = findK(nums1,0,len1-1,nums2,0,len2-1,(len1+len2)/2+1);
if((len1+len2)%2==1) return res1;
else{
double res2 = findK(nums1,0,len1-1,nums2,0,len2-1,(len1+len2)/2);
return (res1+res2)/2.0;
}
}
double findK(vector<int>& nums1,int start1,int end1,vector<int>& nums2,int start2,int end2,int K){
// 处理越界问题
if(start1>end1) return nums2[start2+K-1];
if(start2>end2) return nums1[start1+K-1];
// 终止条件
if(K == 1) return nums1[start1]<nums2[start2]?nums1[start1]:nums2[start2];
// 处理形如 [1;2,3,4,5,6,7] 的 K/2-1 越界问题
if(start1+K/2-1>end1){
int first = nums1[end1];
int second = nums2[start2+K/2-1];
if(first <= second)
return findK(nums1,end1+1,end1,nums2,start2,end2,K-end1-1);
else
return findK(nums1,start1,end1,nums2,start2+K/2,end2,K-K/2);
}
else if(start2+K/2-1>end2){
int first = nums1[start1+K/2-1];
int second = nums2[end2];
if(first <= second)
return findK(nums1,start1+K/2,end1,nums2,start2,end2,K-K/2);
else
return findK(nums1,start1,end1,nums2,end2+1,end2,K-end2-1);
}
else{
int first = nums1[start1+K/2-1];
int second = nums2[start2+K/2-1];
if(first <= second)
return findK(nums1,start1+K/2,end1,nums2,start2,end2,K-K/2);
else
return findK(nums1,start1,end1,nums2,start2+K/2,end2,K-K/2);
}
}
};