寻找两个有序数组的中位数 Median of Two Sorted Arrays
题目
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
示例
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
思路
参考链接:寻找两个有序数组的中位数 C / C++
在这道题中,首先需要明确 中位数定义,即将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。而题目难点在于时间复杂度要求为O(log(m + n)),而如果简单使用两数组合并求中位数则时间复杂度通常为O(m+n),不符合题意要求。参考数据结构中对于不同方式查找的时间复杂度要求,可以很明显发现利用二分查找法最简单且符合题目要求。
常用算法时间复杂度参考链接:常用排序算法和查找算法的时间复杂度和空间复杂度
上述链接中详细介绍了二分切割法在两个数组中的应用,我就不再过多赘述了。而其中值得我借鉴的一个思路在于,利用虚拟 ‘#’ 填充,使得数组中两数组长度恒为偶数,即让m转换成2m+1 ,n转换成2n+1, 两数之和就变成了2m+2n+2,恒为偶数。把2个数组看做一个虚拟的数组A,A有2m+2n+2个元素,割在m+n+1处,所以我们只需找到m+n+1位置的元素和m+n+2位置的元素就行了。
左边:A[m+n+1] = Max(LMax1,LMax2)
右边:A[m+n+2] = Min(RMin1,RMin2)
==>Mid = (A[m+n+1]+A[m+n+2])/2 = (Max(LMax1,LMax2) + Min(RMin1,RMin2) )/2
另,此处涉及C/C++中关于整型上下限INT_MAX和INT_MIN的用法,参考链接:【C/C++】整型上下限INT_MAX INT_MIN及其运算
初解
参考上述思路得到代码:
#define max(a,b) (((a)>(b))?(a):(b))
#define min(a,b) (((a)<(b))?(a):(b))
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n=nums1.size();
int m=nums2.size();
if(n>m){ //若nums2长度小于nums1,交换两数组,保证nums1长度最短
return findMedianSortedArrays(nums2, nums1);
}
int LMax1,LMax2,RMin1,RMin2,c1,c2;
// Ci 为第i个数组的割,比如C1为2时表示第1个数组只有2个元素。LMaxi为第i个数组割后的左元素。RMini为第i个数组割后的右元素。
int low=0,high=2*n; //加入虚拟#后nums1长度为2*n
while(low<=high){ //二分切割
c1 = (low+high)/2; //c1是二分的结果
c2 = m+n-c1;
/*关于c2 = m + n - c1;的理解:
1、两个虚拟数组[#2#3#5#][#1#4#7#9#]合并之后的数组A[#1#2#3#4#5#7#9#],长度为2*(m+n)+2,多余了一个#,忽略掉这个#,有效长度为2*(m+n)+1;
2、求数组A的中位数转换成了求2*(m+n)+1长度数组的中位数,也就是求第m+n+1位置的元素的值;
3、c1、c2都是下标,从0开始,那么减去1,就是c2=m+n-c1;*/
LMax1 = (c1 == 0) ? INT_MIN : nums1[(c1 - 1) / 2]; //c1=0则LMax1为数组nums1第一个元素,即左边数组为空,假定LMax1 = INT_MIN,来保证LMax2<RMin1恒成立
RMin1 = (c1 == 2 * n) ? INT_MAX : nums1[c1 / 2]; //c1=2*n则Rmin1为数组nums1最后一个元素,即右边数组为空,假定RMin1= INT_MAX,来保证LMax2<RMin1恒成立
LMax2 = (c2 == 0) ? INT_MIN : nums2[(c2 - 1) / 2]; //nums2数组同理
RMin2 = (c2 == 2 * m) ? INT_MAX : nums2[c2 / 2];
if(LMax1 > RMin2){ //二分法移动low和high
high = c1-1;
}else if(LMax1 > RMin1){
low = c1+1;
}else{
break;
}
}
return (max(LMax1,LMax2)+min(RMin1,RMin2))/2.0;
}
};
运行结果:
But,提交后不成功,执行错误,原因是测试用例覆盖不完全:
修正代码:
原因竟然是最后else if时判断条件写错了…
#define max(a,b) (((a)>(b))?(a):(b))
#define min(a,b) (((a)<(b))?(a):(b))
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n=nums1.size();
int m=nums2.size();
if(n>m){ //若nums2长度小于nums1,交换两数组,保证nums1长度最短
return findMedianSortedArrays(nums2, nums1);
}
int LMax1,LMax2,RMin1,RMin2,c1,c2;
// Ci 为第i个数组的割,比如C1为2时表示第1个数组只有2个元素。LMaxi为第i个数组割后的左元素。RMini为第i个数组割后的右元素。
int low=0,high=2*n; //加入虚拟#后nums1长度为2*n
while(low<=high){ //二分切割
c1 = (low+high)/2; //c1是二分的结果
c2 = m+n-c1;
/*关于c2 = m + n - c1;的理解:
1、两个虚拟数组[#2#3#5#][#1#4#7#9#]合并之后的数组A[#1#2#3#4#5#7#9#],长度为2*(m+n)+2,多余了一个#,忽略掉这个#,有效长度为2*(m+n)+1;
2、求数组A的中位数转换成了求2*(m+n)+1长度数组的中位数,也就是求第m+n+1位置的元素的值;
3、c1、c2都是下标,从0开始,那么减去1,就是c2=m+n-c1;*/
LMax1 = (c1 == 0) ? INT_MIN : nums1[(c1 - 1) / 2]; //c1=0则LMax1为数组nums1第一个元素,即左边数组为空,假定LMax1 = INT_MIN,来保证LMax2<RMin1恒成立
RMin1 = (c1 == 2 * n) ? INT_MAX : nums1[c1 / 2]; //c1=2*n则Rmin1为数组nums1最后一个元素,即右边数组为空,假定RMin1= INT_MAX,来保证LMax2<RMin1恒成立
LMax2 = (c2 == 0) ? INT_MIN : nums2[(c2 - 1) / 2]; //nums2数组同理
RMin2 = (c2 == 2 * m) ? INT_MAX : nums2[c2 / 2];
if(LMax1 > RMin2){ //二分法移动low和high
high = c1-1;
}else if(LMax2 > RMin1){
low = c1+1;
}else{
break;
}
}
return (max(LMax1,LMax2)+min(RMin1,RMin2))/2.0;
}
};