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

本文探讨了两种解法来找到两个有序数组的中位数。解法一是通过确定第n/2个元素,解法二是利用二分查找的思想剔除不可能是中位数的元素。在编程实现时,需要注意整数除法、数组越界等常见问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

解法一:
当合并后的总元素个数是奇数时,中位数的下标是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等问题结果调试了很久。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值