题目:LeetCode 004 Median of Two Sorted Arrays
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)).
题意:给出两组已经排好序的数组,分为有m和n个整数,找到两个数组按顺序合并之后的中位数,使得复杂度为O(log(m+n)).
首先需明确中位数的概念,分两种情况,当合并后的数组 C[] 中个数T个,当T为奇数时,C++数组从0开始,第T/2+1个数C[T/2]为中位数,当T为偶数时,中位数为第T/2个数C[T/2-1] 和 第T/2+1个数 C[T/2]的和的一半。有下面三种思路。
思路一:利用C++最简单的思路,涉及到排序用快排sort,将两个数组合并后排序找到中位数,复杂度为O((n+m)log(n+m))。
思路二:利用已知的两个数组都有序这个特点,顺次合并排序,复杂度为O(m+n)。另外网上有一种方法也是O(m+n)的算法,用两个指针分别记录个数然后找到中位数,我尝试了一下发现边界条件太多放弃了。
我的代码如下:
class Solution {
public:
double findMedianSortedArrays(vector<int>& a, vector<int>& b) {
vector<int> c;
int m = a.size(), n = b.size(), i = 0, j =0;
while(i < m && j < n)
{
if(a[i] < b[j])
c.push_back(a[i++]);
else if(a[i] > b[j])
c.push_back(b[j++]);
else
{
c.push_back(a[i++]);
c.push_back(b[j++]);
}
}
while(i < m) c.push_back(a[i++]);
while(j < n) c.push_back(b[j++]);
int t = m + n;
if(t&1) return c[t/2];
return 0.5*(c[t/2]+c[t/2-1]);
}
};
思路三:http://blog.csdn.net/doc_sgl/article/details/13081925
这个思路是将求中位数转化成了求两个排好序的数组的第K小的数。求中位数,即令K=T/2 或 K=T/2+1即可。
首先,两个排好序的数组A[], B[]分别有m和n个,求第k小的数。假设合并后的数组为C[],个数为m+n。
其次,要明确一个规律。将 k 分成两部分 k = pa + pb,也就是说合并后的数组的前k个数是由数组A[]的前pa个数A[0]...A[pa-1]和数组B[]的前pb个数B[0]...B[pb-1]组成。
比较数组A[]中的第pa个数和数组B[]中的第pb个数。
(1)如果A[pa-1] == B[pb-1],那么合并之后刚刚好第k=pa+pb个数即为A[pa-1]或者B[pb-1]。(递归终点1)
(2)如果A[pa-1] < B[pb-1],那么数组A中的前pa个数一定不会是数组C的第k个数。证明很简单,反证,假设A[pa-1]为第k个数,则A中有pa-1个数在C[k-1]前面,因为A[pa-1]<B[pb-1],B中之多有pb-1个数在C[k-1]前面,那么C中最多有(pa-1)+(pb-1)=pa+pb-2=k-2个数在C[k-1]前面,坐标从0开始,矛盾。
这样,我们就可以把数组A中的前pa个数删掉,求A[pa]..A[m-1] 与数组B合并之后的第k-pa小的数。
(3)同理,如果A[pa-1] > B[pb-1],那么数组B中的前pb个数一定不会是数组C的第k个数。
对于把k拆分成两部分,最简单的思路即二分,取 pa = k/2,但是有可能m < k/2,则此时取pa = m。
这样我们就将这个问题变成了一个同类的子问题,可以用递归的思路来解。递归的话,就需要考虑递归的终点:
(1)如果A或者B为空,则直接返回B[k-1]或者A[k-1];
(2)如果k为1,我们只需要返回A[0]和B[0]中的较小值;
(3)如果A[k/2-1]=B[k/2-1],返回其中一个;
思路看懂了,但是我自己的样例一直都过不了,递归还是学的不好,在一层层递归下去进入子问题时,我懂,中间调试结果也是对的,但是到达递归终点返回之后,进入上一层,我就搞不懂了。
终于过了,原因在于,子问题没有返回值。标红的三个地方出错,递归到达终点之后回返回上一层递归的函数,如果是有返回值的递归函数,加上return会在每一层递归的时候直接函数结束再次返回到上上一层的递归
由于每次删掉前面一半的元素时利用了数组首地址移动,所以写成C的代码如下:
int findKth(int a[], int m, int b[], int n, int k)
{
int pa, pb;
if(m > n)
return findKth(b, n, a, m, k);
if(m == 0)
return b[k-1];
if(k == 1)
return a[0] < b[0] ? a[0] : b[0];
pa = k/2 < m ? k/2 : m;
pb = k-pa;
if(a[pa-1] < b[pb-1])
return findKth(a+pa, m-pa, b, n, k-pa);
else if(a[pa-1] > b[pb-1])
return findKth(a, m, b+pb, n-pb, k-pb);
return a[pa-1];
}
double findMedianSortedArrays(int *nums1, int m, int *nums2, int n)
{
int t = m+n;
if(1&t) return findKth(nums1, m, nums2, n, t/2+1);
return 0.5*( findKth(nums1, m, nums2, n, t/2+1) + findKth(nums1, m, nums2, n, t/2) );
}
然后跟同学请教发现如果是C++利用vector没有办法直接利用数组首地址移动时,就需要记录vector<int>的开始位置,然后就能O(1)访问vector了。代码如下:
class Solution {
public:
int findKth(vector<int> a, int m, vector<int> b, int n, int k)
{
int lena = a.size() - m, lenb = b.size() - n;
if(lena > lenb) return findKth(b, n, a, m, k);
if(lena == 0) return b[n+k-1];
if(k == 1) return min(a[m], b[n]);
int pa = min(k/2, lena), pb = k - pa;
int ida = m + pa - 1, idb = n + pb - 1;
if(a[ida] < b[idb]) return findKth(a, m+pa, b, n, k-pa);
else if(a[ida] > b[idb]) return findKth(a, m, b, n+pb, k-pb);
return a[ida];
}
double findMedianSortedArrays(vector<int>& a, vector<int>& b)
{
int t = a.size() + b.size();
if(0x1&t) return findKth(a, 0, b, 0, t/2+1);
return 0.5*(findKth(a, 0, b, 0, t/2) + findKth(a, 0, b, 0, t/2+1));
}
};