问题描述:
There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).
总结大牛的算法如下:
假设nums1,nums2的大小分别为m,n。
将有序系列nums1分为左右两个部分,有m中分法,如下所示为第i中分法:
| | | | | i | | |
| nums1[0] | nums1[1] | nums1[2] | nums1[3]...| nums1[i-1] | nums1[i] ....| nums1[m-2] | nums1[m-1] |
同样对nums2有n种分法,讲nums1,nums2左边和左边的序列组合为一个新的集合numsLeft,同样可以得到numsRight
如果能满足一下两个条件,就能容易的得到中位数:
- numsLeft.size() == numsRight.size() 或者 numsLeft.size() == numsRight.size() +1
- 左边集合中的数 <= 右边集合中的数
假设nums1,nums2分别在i,j处分割,则要满足:
- i+j=m-i+n-j or i+j=m-i+n-j+1
- nums1[i-1] <= nums2[j] and nums2[j-1] <= nums1[i]
对于i==0 or m, j==0 or n的情况 i-1,j-1会超出数组索引范围,需要讨论,所以思路如下,i从0到m依次递增,得到nums1的一种分法,由最终左右两边数字的个数相等,得到对应的nums2的分割方法,比较如果满足
nums1[i-1] <= nums2[j] and nums2[j-1] <= nums1[i],则找到了符合要求的分法,跳出,求中位数。
我们必须保证nums1.size()<=nums2.size(),这样先对一个小的数组分割,不管怎么分都能对大的数组分割满足左右个数相等的要求。
代码如下:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
if(nums1.size() > nums2.size())
return findMedianSortedArrays(nums2,nums1);
decltype(nums1.size()) i=0,j=0,m=nums1.size(),n=nums2.size();
if(m==0)
if(n%2)
return nums2[n/2];
else
return (nums2[n/2-1]+nums2[n/2])/2.0;
for(i=0;i<=m;i++)
{
j=(n+m+1)/2-i;
if(i==0 || j==n)
if(nums1[i] >= nums2[j-1])
break;
else
continue;
else if(i==m || j==0)
if(nums1[i-1] <= nums2[j])
break;
else
continue;
else
{
if(nums1[i-1] <= nums2[j] && nums2[j-1] <=nums1[i])
break;
}
}
cout<<"i "<<i<<endl;
cout<<"j "<<j<<endl;
//left set
int numleft=0,numright=0;
if(i==0)
numleft=nums2[j-1];
else if(j==0)
numleft=nums1[i-1];
else
numleft=max(nums1[i-1],nums2[j-1]);
if((n+m)%2)
return numleft;
else
{
if(i==m)
numright=nums2[j];
else if(j==n)
numright=nums1[i];
else
{
numright=min(nums1[i],nums2[j]);
}
return (numleft+numright)/2.0;
}
}
};
说明:
- 对于从0开始编号的数组,如何取到中间的值? 如果m为奇数, nums[(m-1)/2],因为去尾的原因也可以直接写成nums[m/2],当m为偶数,是中间两个数的平均值 (nums[m/2]+nums[m/2-1])/2.0。
- 如果是从1开始编号的数组,m为奇数时应为nums1[(m+1)/2],当m为偶数时,为(nums[m/2]+nums[m/2+1])/2.0。
- 为什么j=(m+n)/2-i 或者 j=(m+n+1)/2-i,在程序中用j=(m+n+1)/2-i就可以完全表示,不需要分类讨论?因为当m+n为奇数时,左边要比右边多分一个数,j=(m+n+1)/2-i。当m+n为偶数时,(m+n+1)/2和(m+n)/2结果是一样的,所以不用分类讨论。
- 在3的问题上扩展,3/2==1, -3/2==-1,直接去尾
- 当nums1.size() > nums2.size()调换一下数组的位置即可
- 当m+n为奇数时,中间值出现在numsLeft,取max(nums1[i-1],nums2[j-1]),但是需要讨论边界情况,当m+n为偶数时,取numsLeft得最大值和numsRight的最小值,然后取平均即可,同样要考虑边界情况
以上的代码是i从0到m递增,一次验证分割方法是否满足要求,因为nums1,nums2都是有序数列,可以使用二分法进行搜索,修改如下:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
if(nums1.size() > nums2.size())
return findMedianSortedArrays(nums2,nums1);
decltype(nums1.size()) i=0,imin=0,imax=nums1.size(),m=imax,n=nums2.size(),j=0,half=(m+n+1)/2;
if(m == 0)
{
if(n%2)
return nums2[n/2];
else
return (nums2[n/2-1]+nums2[n/2])/2.;
}
while(imin <= imax)
{
i=(imin+imax)/2;
j=half-i;
if(i ==0 || j == n)
{
if(nums1[i] < nums2[j-1])
imin=i+1;
else break;
}
else if(j ==0 || i == m)
{
if(nums1[i-1] > nums2[j])
imax=i-1;
else break;
}
else
{
if(nums1[i-1] > nums2[j])
imax=i-1;
else if(nums2[j-1] > nums1[i])
imin=i+1;
else
break;
}
}
cout<<"i is "<<i<<endl;
cout<<"j is "<<j<<endl;
int numLeft=0,numRight=0;
if(i==0)
numLeft=nums2[j-1];
else if(j==0)
numLeft=nums1[i-1];
else
numLeft=max(nums1[i-1],nums2[j-1]);
if((n+m)%2)
return numLeft;
else
{
if(i==m)
numRight=nums2[j];
else if(j==n)
numRight=nums1[i];
else
numRight=min(nums1[i],nums2[j]);
return (numLeft+numRight)/2.;
}
}
};
最后放一个大神的代码吧:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size(), n = nums2.size();
if (m > n) return findMedianSortedArrays(nums2, nums1);
int i, j, imin = 0, imax = m, half = (m + n + 1) / 2;
while (imin <= imax) {
i = (imin & imax) + ((imin ^ imax) >> 1);
j = half - i;
if (i > 0 && j < n && nums1[i - 1] > nums2[j]) imax = i - 1;
else if (j > 0 && i < m && nums2[j - 1] > nums1[i]) imin = i + 1;
else break;
}
int num1;
if (!i) num1 = nums2[j - 1];
else if (!j) num1 = nums1[i - 1];
else num1 = max(nums1[i - 1], nums2[j - 1]);
if ((m + n) & 1) return num1;
int num2;
if (i == m) num2 = nums2[j];
else if (j == n) num2 = nums1[i];
else num2 = min(nums1[i], nums2[j]);
return (num1 + num2) / 2.0;
}
};