Idiot-maker

  :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

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,所以要拿出来单独讨论。

posted on 2015-01-21 20:36  NickyYe  阅读(186)  评论(0编辑  收藏  举报