题目描述
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)).
Example 1:
nums1 = [1, 3]
nums2 = [2]
The median is 2.0
Example 2:
nums1 = [1, 2]
nums2 = [3, 4]
The median is (2 + 3)/2 = 2.5
分析
这题就是给定两个排好序的数组,求这两个数组的中位数。很自然的思路是:将两个数组合并然后按照中位数的定义来求。合并数组这一步不一定要全部做完,只用找到中间一个数(长度为奇数)或者两个数(长度为偶数)即可,也就是说可以只重排一半的数就可以了。直接套用合并数组的算法即可。但是我这种算法的复杂度为O((m+n)/2),虽然没有达到要求,但是过了。。。所以肯定有更好的方法。
反观上面的这种方法,我们要找到中位数必须得遍历前一半的数。如果我们每一步能跳过一半的数,不用遍历,自然复杂度就降低了。举个例子假设num1=[1,2,3,4],num2=[5,6,7,8],按之前的算法,我们必须得遍历1,2,3,4,如果我们以上帝模式知道1,2,3,4一定比中位数小,那么是不是可以一次过2个数?换句话说一次删去2个数。现在就是得实现这种上帝模式了。
其实这是一个找第k大元素的问题,现在假设中位数是第k大的元素,现在我们要实现的就是每次过k/2个元素,直到找到这个元素为止。首先考虑最极端的情况:(1)前k个元素都在num1或num2中,那么自然地我们直接去掉num1或num2的k/2个元素;(2)前k个元素一半在num1,一半在num2,至于去掉哪一部分,后面再说。考虑了这两种情况,你会发现如果其中一个数组包含大于或等于k/2个比中位数小的数,那么一定可以剔除这个数组的k/2个数。那么怎么判断这一条件呢?假设num1是满足上述条件的数组,我们在合并数组的时候,当我们已经把num1中的前k/2个数放进新数组的时候,我们会判断第k/2+1个数与num2中接下来要比较的一个数进行比较,如果仍比较小就将它继续加入新数组,是不是意味着这个数肯定也比num2中的第k/2个数小(如果存在的话)?所以我们发现只用比较两个数组的第k/2个数的大小就可以知道可以删除哪个数组的前k/2个数。这里只是给出我的思考过程,说的可能不是很清楚,不过希望对大家有用,具体的证明可以参考http://blog.csdn.net/yutianzuijin/article/details/11499917/.
代码
1.最初的代码O((m+n)/2)(写的好长,有点不好意思)
这里面num1,num2分别用来存储最中间的两个数,当数组长度为偶数的时候,中位数就是两个数的平均;当数组长度为奇数时,中位数就是num2。
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int size1 = nums1.size(), size2 = nums2.size(), count = 0;
int i = 0, j = 0, edge = (size1 + size2) / 2;
double num1 = 0, num2 = 0;
while(i < size1 && j < size2 && count < edge + 2){
int a = nums1[i], b = nums2[j];
if(a < b){
i++;
count++;
if(count == edge)
num1 = a;
if(count == edge + 1)
{
num2 = a;
break;
}
}
else{
j++;
count++;
if(count == edge)
num1 = b;
if(count == edge + 1)
{
num2 = b;
break;
}
}
}
while(i < size1 && count < edge + 2)
{
int a = nums1[i];
i++;count++;
if(count == edge)
num1 = a;
if(count == edge + 1)
{
num2 = a;
break;
}
}
while(j < size2 && count < edge + 2)
{
int a = nums2[j];
j++;count++;
if(count == edge)
num1 = a;
if(count == edge + 1)
{
num2 = a;
break;
}
}
if((size1+size2)%2==0)
return (num1 + num2)/2.0;
else
return num2;
}
};
2.缩短后的代码
代码虽然缩短了,但是运行时间变长了(个人认为是多了函数调用)而且缩短的不多,果然还是路还是很长。
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int size1 = nums1.size(), size2 = nums2.size(), count = 0;
int i = 0, j = 0, edge = (size1 + size2) / 2;
double num1 = 0, num2 = 0;
while(i < size1 && j < size2 && count < edge + 1){
int a = nums1[i], b = nums2[j];
if(a < b){
i++;
getMedian(num1,num2,count,a,edge);
}
else{
j++;
getMedian(num1,num2,count,b,edge);
}
}
while(i < size1 && count < edge + 1)
{
int a = nums1[i];
i++;
getMedian(num1,num2,count,a,edge);
}
while(j < size2 && count < edge + 1)
{
int a = nums2[j];
j++;
getMedian(num1,num2,count,a,edge);
}
if((size1+size2)%2==0)
return (num1 + num2)/2.0;
else
return num2;
}
void getMedian(double &num1, double &num2, int &count, int value, int median)
{
count++;
if(count == median)
num1 = value;
if(count == median + 1)
num2 = value;
}
};
3.第k大元素算法
下面这段代码应该比我之前写的看的要更清晰舒服,虽然花了挺多时间,但是我骄傲!有一点要注意的是这里面有一个vector转数组传入函数的过程,一定要注意,如果vector是空的是不能直接把向量首元素地址传进函数里的(因为它根本没有首元素)。其实笔者觉得还能优化,因为在总长度为偶数的情况下进行了两次findKth函数调用而这两次函数调用的过程大部分是相同的,如果两次变一次肯定能更快,更好,大致思路是动态规划,如果大家有什么更好的方法或是笔者写的有什么错误,欢迎评论交流!共同进步!
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int length = nums1.size() + nums2.size();
int *a = nums1.size()==0?new int:&nums1[0],*b = nums2.size()==0?new int:&nums2[0];
if(length%2)
return findKth(a,b,nums1.size(),nums2.size(),length/2+1);
else
return (findKth(a,b,nums1.size(),nums2.size(),length/2)+findKth(a,b,nums1.size(),nums2.size(),length/2+1))/2.0;
}
int findKth(int* a, int* b, int size1, int size2, int k)
{
if(k == 1)
if(size1 == 0) return b[0];
else if(size2 == 0) return a[0];
else return a[0]<b[0]?a[0]:b[0];
if(k/2 > size1)
return findKth(b,a,size2,size1,k);
if(k/2 > size2)
return findKth(a+k/2,b,size1-k/2,size2,k-k/2);
else if(a[k/2 - 1] <= b[k/2 - 1])
return findKth(a+k/2,b,size1-k/2,size2,k-k/2);
else
return findKth(a,b+k/2,size1,size2-k/2,k-k/2);
}
};