https://leetcode.com/problems/median-of-two-sorted-arrays/description/
There are two sorted arrays A and B 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)).
下面是O(nlgn)的解法
public class Solution { public double findMedianSortedArrays(int A[], int B[]) { int[] C = new int[A.length + B.length]; for(int i = 0; i < A.length; i++){ C[i] = A[i]; } for(int i = 0; i < B.length; i++){ C[A.length + i] = B[i]; } Arrays.sort(C); if((A.length + B.length) % 2 == 1){ return C[(A.length + B.length) / 2]; }else{ return (C[(A.length + B.length) / 2] + C[(A.length + B.length) / 2 - 1]) / 2d; } } }
还有O(m+n)的解法,两个对象指向数组开头,往后移动,直到找到第m+n/2大的。
public class Solution { public double findMedianSortedArrays(int A[], int B[]) { int i =0; int j = 0; int m = A.length; int n = B.length; double median = 0d; double last = 0d; int count = 0; while(count <= (m + n) / 2){ last = median; //如果A中的元素已经用完,直接取B数组 if(i == m){ median = B[j]; j++; }else if(j == n){//如果B中的元素已经用完,直接取A数组 median = A[i]; i++; }else if(A[i] < B[j]){ median = A[i]; i++; }else if(A[i] >= B[j]){ median = B[j]; j++; } count ++; } if((A.length + B.length) % 2 == 1){ return median; }else{ return (median + last) / 2d; } } }
最后提到的是一个极为巧妙地方法。可以在O(logm+logn)的时间内解决问题。看到时间复杂度很容易想到分治求解,可分治的模型很难确定,这里给了一个比较巧妙的思路。假定m+n为奇数,我们只要求第(m+n)/2 +1个,偶数则是求(m+n)/2 +1与(m+n)/2大数的平均数。我们令(m+n)/2+1=k。就是求A+B中第K大数的问题,只不过中位数正好是一半。
考虑在A和B中各取一半,即K/2的数字进行比较。如果这时候A[k/2- 1] < B[k/2 - 1],证明A[k/2 -1]往左的数字,都在前k小的数字里,真正的临界点在A[k/2 -1]往后,那么就把A前面的舍弃,从A[k/2]开始,包括B的所有数字,寻找剩下的第k/2个。同样,如果B[k/2 - 1]小,证明这个第k大的还在B[k/2 - 1]之后,于是舍弃B[k/2 - 1]往前的数字,在剩下的中继续寻找。如此形成一个分治的解决方法。
这里要注意,如果k/2>A的元素数量,就代表A中的元素全部在第K大数字之前,全部舍去。实际上就是直接在B中找第k-m-1个了。
public class Solution { public static double findKthElement(int A[], int B[], int k){ int m = A.length; int n = B.length; //always assume that m is equal or smaller than n if(m > n){ return findKthElement(B, A, k); } if(m == 0 & n == 0){ return 0; } if(m == 0){ return B[k - 1]; } if(n == 0){ return A[k - 1]; } if (k == 1) return Math.min(A[0], B[0]); int pa = Math.min(m, k / 2); int pb = k - pa; //递归直至该条件结束 if(A[pa - 1] == B[pb - 1]){ return A[pa - 1]; }else if(A[pa - 1] < B[pb - 1]){//舍去A[pa-1]之前的元素,重新搜索第k-pa个元素 int[] C = new int[m - pa]; for(int i = 0; i < m - pa; i++){ C[i] = A[pa + i]; } return findKthElement(C, B, k - pa); }else{//舍去B[pb-1]之前的元素,重新搜索第k-pb个元素 int[] C = new int[n - pb]; for(int i = 0; i < n - pb; i++){ C[i] = B[pb + i]; } return findKthElement(A, C, k - pb); } } public double findMedianSortedArrays(int A[], int B[]) { int m = A.length; int n = B.length; if((m + n) % 2 == 1){ return findKthElement(A, B, (m + n) / 2 + 1); }else{ return (findKthElement(A, B, (m + n) / 2) + findKthElement(A, B, (m + n) / 2 + 1)) / 2; } } }
update:2015/03/14
再次做这题,还是被搞得晕死。我只能说第k大和数组下标从0开始简直是要搞死人。
K个元素的下标就是k-1。数组元素的数量是end-start+1类似这种,还有k/2-1。稍有不注意就会做错。
public class Solution { public double findMedianSortedArrays(int A[], int B[]) { if((A.length + B.length) % 2 == 1){ return findKthSrotedArrays(A, 0, A.length - 1, B, 0, B.length - 1, (A.length + B.length) / 2 + 1); }else{ return (findKthSrotedArrays(A, 0, A.length - 1, B, 0, B.length - 1, (A.length + B.length) / 2) + findKthSrotedArrays(A, 0, A.length - 1, B, 0, B.length - 1, (A.length + B.length) / 2 + 1)) / 2d; } } //第k大的下标在k-1,第k/2的坐标在k/2-1 public int findKthSrotedArrays(int[] A, int startA, int endA, int[] B, int startB, int endB, int k){ //假设A长度小于B,否则就反过来搜,这样只要考虑A和k/2-1的大小就可以了 if(endA- startA > endB - startB){ return findKthSrotedArrays(B, startB, endB, A, startA, endA, k); } if(startA > endA){ return B[startB + k - 1]; } if(startB > endB){ return A[startA + k - 1]; } if (k == 1){ return Math.min(A[startA], B[startB]); } int pa = Math.min(startA + k / 2 - 1, endA); //A中元素有k/2个,坐标为k/2-1 + startA int pb = startB + k - (pa - startA + 1) - 1; if(A[pa] == B[pb]){ return A[pa]; }else if(A[pa] < B[pb]){ return findKthSrotedArrays(A, pa + 1, endA, B, startB, endB, k - (pa - startA)- 1); }else{ // if(A[pa - 1] > B[pb - 1]) return findKthSrotedArrays(A, startA, endA, B, pb + 1, endB, k - (pb - startB) - 1); } } }
最后其实用下面的代码可以同时砍去两段,因为另一段已经肯定不在前k个元素里了。
if(A[pa] == B[pb]){ return A[pa]; }else if(A[pa] < B[pb]){ return findKthSrotedArrays(A, pa + 1, endA, B, startB, pb, k - (pa - startA)- 1); }else{ // if(A[pa - 1] > B[pb - 1]) return findKthSrotedArrays(A, startA, pa, B, pb + 1, endB, k - (pb - startB) - 1); }
最后总结一下找第k大元素的方法。
二分查找的划分条件:比较A[k/2 - 1]和B[k/2 - 1]的大小,二不是比较A和B各自当中的元素大大小(记得上面说的,第k大和数组下标从0开始的痛苦)
如果A[k/2 - 1]<B[k/2 - 1],就代表A的窗口要往右扩,B的窗口往左缩,但是它们的含义是不同的。A[k/2 - 1]往左的元素肯定在前k大的元素里,故要舍去,B[k/2-1]往右的元素肯定不在前k/2大的元素里,所以也要舍去。
如果A[k/2 - 1]>B[k/2 - 1],A的窗口往左缩,B的窗口往右扩。也就是舍去B左侧的元素,因为它们都在前k大元素里。
如果A[k/2 - 1]==B[k/2 - 1],返回该值,就是第k大的值。
需要考虑的边界情况:
k/2 - 1很可能大于a.length - 1或者b.length - 1,所以需要判断它们的大小。方便的做法是,永远假设A<B或者A>B。
如果A已经被全部砍去,只要在B中找到剩下第K大的元素即可,反之亦然。
k==1时,不能再看k/2-1了,这时它==-1,所以要拿出来单独讨论。