【Leetcode】两个有序数组的中位数

来源:互联网 发布:python中import用法 编辑:程序博客网 时间:2024/05/22 15:34

解法一:
当合并后的总元素个数是奇数时,中位数的下标是n/2。当总元素个数是偶数时,中位数是下标n/2-1和下标n/2两个元素的平均值。不论总个数奇偶,可以将n/2作为右中位数,n/2-1作为左中位数,只不过总个数是奇数时,没用到左中位数。也就是说必须要找到第n/2+1个元素。

    private static double findMedian(int[] A,int[] B){        if(A==null||B==null) return -1;        int m=A.length;        int n=B.length;        int len=m+n;        if(len==0) return -1;        if(n==0) return findMedian(B,A);        int left=-1,right=-1;        int aStart=0,bStart=0;        for(int i=0;i<=len/2;i++){            left=right;            if(aStart<m&&(bStart>=n||A[aStart]<B[bStart])){                right=A[aStart++];            }else{                right=B[bStart++];            }        }        if((len&1)==0) return (left+right)/2.0;        else return right;    }

上面的方法还可以推广到更普遍的情况,找出两个有序数组合并后的第k个元素。

    private static int findKth(int[] A,int[] B,int k){        if(A==null||B==null) return -1;        int m=A.length;        int n=B.length;        int len=m+n;        if(len<k||len==0) return -1;        if(m==0) return B[k-1];        if(n==0) return A[k-1];        int c=0;        int aStart=0,bStart=0;        int result=-1;        while(c<k){            if(aStart<m&&(bStart>=n||A[aStart]<B[bStart])){                result=A[aStart++];            }else{                result=B[bStart++];            }               c++;        }        return result;    }

解法二:
解法一的实质是每次剔除一个不可能是中位数的元素。其实也可以利用二分查找的思想每次剔除大范围元素提高搜索效率。因为查找中位数实质上是查找第k个元素。
对于数组A前p个元素A[0]、A[1]、A[2]、……A[p-1],有p-1个元素不大于A[p-1],同样数组B前k-p个元素B[0]、B[1]、B[2]、……B[k-p-1],有k-p-1个元素不大于B[k-p-1]。比较A[p-1]和B[k-p-1]存在三种情况:
1、若A[p-1]=B[k-p-1],那么共有p-1+(k-p-1)+1个元素不大于A[p-1],也就是说A[p-1]即是第k小的数。
2、若A[p-1]< B[k-p-1],那么第k小的数不可能在区间A[0,p)之内。我们可以用反证法证明,若第k小的数在A[0,p)中设为A[i],那么A[i]前面只有i个数不比它大,不妨设A[p-1]是第k小的数即i=p-1,也就是说最多只有p-1个数不比它大,而B[k-p-1]大于A[p-1],所以B[k-p-1]之后的所有数都大于A[p-1],这样即使B[p-k-1]之前的所有数都不比A[p-1]大,B中也只能找到k-p-1个数不比A[p-1]大。那么A和B中最多只有p-1+(k-p-1)=k-2个数不大于A[p-1],从而A[p-1]不是第k小的数。
这里要注意推论第k小的数不在某区间的前提是同一数组中元素的相对位置不变,只有之前的数不比某个数大,之后的数不论是否相等都比它“大”。如122234中第2个2只有两个数1、2不比它大,第3个2不算在内。否则,第k小的数可能出现在任何位置,如A={1,1,1,1,1},B={1,1,1,1,1}。 但是即使认为第k小的数可以出现在任何位置,上面的推论也没有错,因为除了区间A[0,p)之外必定会出现一个第k小,而我们只需要找到一个就可以了,因此舍弃掉区间A[0,p)是可行的。如A={0,1,1}和B={0,1,2,4,5,6}找第5小的数字,舍弃掉A(0,1)仍然可以找到解1。
同样我们可以通过反证法证明,B[k-p-1]之后的数不可能是第k小的数,因为它至少是B[k-p],前面有k-p个数不比B[k-p]大,再加上A[0,p)有p个数不比B[k-p]大,所以至少有k-p+p=k个数不比B[k-p-1]大,也就是说B[k-p]至少是第k+1小的数而不可能是第k小的数。 舍弃区间之后问题就变成寻找第k-p小的数了。
3、同理若A[p-1]>B[k-p-1]可以舍弃掉B[0,p)区间和A[p-1]之后的区间。

    public static double findMedianSortedArrays(int A[], int B[]) {        if(A==null||B==null) return -1.0;        int m=A.length;        int n=B.length;        int len=m+n;        if((len&1)==0){            return (findK(A,0,m-1,B,0,n-1,len/2)+findK(A,0,m-1,B,0,n-1,len/2+1))/2.0;        }else{            return findK(A,0,m-1,B,0,n-1,len/2+1);        }    }    //k表示第k小,对应下标为k-1    public static int findK(int[] A,int startA,int endA,int[] B,int startB,int endB,int k){        int lenA=endA-startA+1;        int lenB=endB-startB+1;        if(lenA<=0&&lenB>0){            return B[startB+k-1];        }else if(lenB<=0&&lenA>0){            return A[startA+k-1];        }        if(lenA>lenB) {            return findK(B,startB,endB,A,startA,endA,k);        }        if(k==1) {            return Math.min(A[startA],B[startB]);        }        int ka=Math.min(lenA,k/2);        int kb=k-ka;        if(A[startA+ka-1]==B[startB+kb-1]){            return A[startA+ka-1];        }else if(A[startA+ka-1]<B[startB+kb-1]){            return findK(A,startA+ka,endA,B,startB,startB+kb-1,k-ka);        }else{            return findK(A,startA,startA+ka-1,B,startB+kb,endB,k-kb);        }    }

编程时没有注意整数/2结果是取商不是小数,下标越界、数组首指针习惯写成0、A[startA+ka-1]偷懒复制成A[startB+kb-1]却忘记将A改成B等问题结果调试了很久。

0 0
原创粉丝点击