Leetcode 4. Median of Two Sorted Arrays
Given two sorted arrays nums1
and nums2
of size m
and n
respectively, return the median of the two sorted arrays.
找两个已经有序数组合起来中的中位数
两个有序数组,很容易想到归并排序,用归并的思想这道题也很好做,两个数组分别需要扫描一次,时间效率为O(m+n)。 这道题难点在于如何达到要求的O(log(m+n))。
一个中位数可以将数组分成两个部分,小于它的和大于它的。在两个数组中也是一样,每个数组都分为小于它的part和大于它的part。
题目可以转化为如下:p1 和p2两个间隔符将数组分成两部分,small (红线覆盖部分), large(右侧无覆盖部分)。我们要求的就是p1和p2的位置,使得两个part他们所含有的数字个数一样或者只相差1(这里默认让small part多1)。p1,p2位置一旦确定,就知道中位数是谁了。 由于small part 和large part分别是相对于同一个中位数而言,可以知道要求的分part需要满足:large part中每个数都要比small part中来的大。
这里small part的大小一定是m+n的一半,也就是small part里有多少个数字是知道的,这样一旦p1确定,p2也就唯一确定了。(p1仿佛红链条的头,只要牵着它走就好,尾巴不用管)
我们p1每滑动一格,就相当于两个part之间交换了一对元素,如下图,就是用small part的19 换large part的8。
左滑: small_part. num1.max 交换 large_part. num2.min
右滑: small_part. num2.max 交换 large_part.num1.min
我们还没有找到median的时候,一定是因为small part中还含有大于large part的数据:small1 > large2 或 small2 > large1
这个时候,只要判断这个small part中最大的数在哪里,让他交换出去就好了。比如图片里,small part中最大的数肯定是num1最大的数3和num2最大的数19之间产生,我们发现是19,于是向右滑动把19送出去。这样我们一次一个格子滑动,很容易找到median。
这道题给定的log级的时间效率,要想到二分!
刚才的滑动过程中,有没有发现我们只需要一直向右滑动链条就会找到结果,不可能向右再向左反复横跳。比如我们把19送出去的时候,其实你会发现19以及19的右侧,永远都不应该被收紧small part。这也就是说当我每次做出p1向右的操作后,当前p1的左侧都不可能作为p1的结果了,这就和二分是一样的道理。
结论:将寻找中间数转化为寻找p1位置(我们将长的数组换到num1),确定p1的搜索区间,然后二分搜索p1位置。
如何处理超出边界的情况
在一个数组中没有small part的情况,也就是当p1在最左侧,p1 = 0,这个时候small1不存在。不存在说明一定符合large2 > small1, 因此我们可以设置small1 = INT_MIN
small1 = p1==0 ? INT_MIN : nums1[p1-1]
同理可得,当p1很小,p2很大的时候,万一p2超出了数组界限,右边空了没有large2了怎么办?没有就让large2 = INT_MAX,这样判断条件等同于始终成立也就相当于这种情况下不存在这条判断。
Class Solution {
public:
//中位数median,将两个数组都分成两个部分,左边部分small part, 右边部分large part,奇数个的话small part会多一个元素
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size();
int n = nums2.size();
int half = (m + n + 1) / 2; //small part 数量
int p1,p2; //small part在nums1, nums2数组中数量, p2 = half-p1 <= n ——》 p1 >= half-n
//left, right是p1可以动的范围
int left = max(0, half - n);
int right = min(half, m);
while(left < right){
p1 = (left + right)/2;
//cout<<left<<" "<<right<<endl;
//cout<<p1<<endl;
p2 = half - p1;
int small1 = p1 == 0 ? INT_MIN: nums1[p1-1];
int small2 = p2 == 0? INT_MIN: nums2[p2-1];
int large1 = p1 >=m ? INT_MAX: nums1[p1];
int large2 = p2 >=n ? INT_MAX : nums2[p2];
if(small1 > large2){ //nums1 里面 small part分多了
right = p1;
}
else if(small2 > large1){
left = p1 + 1;
}
else
break;
}
p1 = (left + right)/2;
p2 = half - p1;
//cout<<p1<<endl;
int small1 = p1 == 0 ? INT_MIN: nums1[p1-1];
int small2 = p2 == 0? INT_MIN: nums2[p2-1];
int large1 = p1 >=m ? INT_MAX: nums1[p1];
int large2 = p2 >=n ? INT_MAX : nums2[p2];
if((m+n) % 2 == 1){
return double(max(small1, small2));
}
else{
double res1 = double(max(small1, small2));
double res2 = double(min(large1,large2));
return (res1 +res2)/2;
}
}
};