题目如下:
已知两个已从小到大排序的数组nums1[M]和 nums2[N],返回两个数组中所有数字从小到大排序后的中位数。两个数组中可能均有重复的数字。
时间复杂度不允许超过log(N+M).
分析:
看到已排序的、可随机访问的数组,肯定用二分查找。
估计还是有人不知道中位数是什么。中位数就是:若总数为偶数,则返回中间两个数字的平均值;否则返回中间的那个数字。
二分查找的难点在于现在没有办法确定中间点的位置,因为数字分布在两个数组中。既然这样,我们就主动简化一下问题。寻找两个数组中从小到大的第k个数字。
在这里,将特殊问题泛化了,题目反而变得简单了。
当N+M为奇数时,我们要找的中位数为k = (N+M)/2+1。当N+M为偶数时,我们要找的中位数为k=(N+M)/2-1 和 k=(N+M)/2。
好,有了上面的分析,下面我们只用考察 double findKth函数即可。
double findKth(const int* nums1, int realSize1, const int* nums2, int realSize2, int k);
对于k,下面我们考察两个数组中的第k/2个元素的大小。无非三种情况,>、<、=。
当然,可能存在k/2超过数组大小的情况。如果超过其中某一个数组的大小,则本次取该数组的最后一个值,下一次的时候,只用考察另外一个数组就行了。
为避免出现越界问题,选让长度小的数组选择,之后,另外一个数组选择剩下的长度。
int p1 = 0, p2 = 0;
if (realSize1 < realSize2)
{
p1 = std::min(k / 2, realSize1);
p2 = k - p1;
}
else
{
p2 = std::min(k / 2, realSize2);
p1 = k - p2;
}
从上面的分法我们也可以看到,不用区分k为奇数还是偶数,因为划分好了一个,k剩下的所有长度全归另一个了。
1.当nums1[p1-1] > nums2[p2-1]时,我们可以确定num2数组中从nums2[0]到nums2[p2-1]这些数字肯定位于合并之后的第k个数字之前。所以这部分数字对我们没有用处。舍弃之。之后,我们就只用考察nums1[0...M] 和 nums2[p2...N]了。
2.当nums1[p1-1] < nums2[p2-1]时,我们可以确定num1数组中从nums1[0]到nums1[p1-1]这些数字肯定位于合并之后的第k个数字之前。所以这部分数字对我们没有用处。舍弃之。之后,我们就只用考察nums1[p1...M] 和 nums2[0...N]了。
3.当nums1[p1-1] == nums2[p2-1]时,我们可以确定,第k个数字必定出现在nums1[p1-1] 与nums2[p2-1] 这二者中,所以返回任一个均OK。
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int size1 = nums1.size();
int size2 = nums2.size();
int total = size1 + size2;
if(total & 1)
return findKth(nums1.data(), size1, nums2.data(), size2, total/2+1);
else
{
double ret = findKth(nums1.data(), size1, nums2.data(), size2, total/2) +
findKth(nums1.data(), size1, nums2.data(), size2, total/2+1);
return ret/2.0;
}
}
double findKth(const int* nums1, int realSize1, const int* nums2, int realSize2, int k)
{
if(0 == realSize1)
return nums2[k-1];
else if(0 == realSize2)
return nums1[k-1];
if(1 == k)
return nums1[0]>nums2[0]?nums2[0]:nums1[0];
int p1 = 0, p2 = 0;
if (realSize1 < realSize2)
{
p1 = std::min(k / 2, realSize1);
p2 = k - p1;
}
else
{
p2 = std::min(k / 2, realSize2);
p1 = k - p2;
}
if(nums1[p1 - 1] < nums2[p2 - 1])
return findKth(nums1+p1, realSize1 - p1, nums2, realSize2, k - p1);
else if(nums1[p1 - 1] > nums2[p2 - 1])
return findKth(nums1, realSize1, nums2+p2, realSize2-p2, k - p2);
else
return nums1[p1 -1];
}
};
有人考虑不用递归来做,其实这里使用递归只是为了避免繁琐的边界判断问题。如果用迭代,则返回时,要区分k为奇偶,同时p1 p2是取的k/2值,还是取的realSize1、realSize2。麻烦。