两个排序数组的中位数

两个排序数组的中位数

题目描述

        中位数是将两个集合划分为两个长度相等的子集,其中一个子集的元素总是大于另一个子集中的元素。
        设两个升序排列的有序数组A与B,A中包含m个元素,B中包含n个元素,请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))

解题思路

A[0]………………A[i-1] | A[i]………………A[m-1]
B[0]………………B[j-1] | B[j]………………B[n-1]
        记数组A,B的长度分别为m,n,不妨设m<=n,A,B升序排列。i,j分别将A,B分成两个部分。A[i]左侧的部分记作left_A,其他记作right_A,同理,B[j]左侧的部分记作left_B,其他记作right_B。为保证上述的条件,我们在代码中做如下处理:

/**
 * 确保A的长度小于等于B的长度
 */
int m = A.length;
int n = B.length;
if (m > n) { // to ensure m<=n
    int[] temp = A;
    A = B;
    B = temp;
    int tmp = m;
    m = n;
    n = tmp;
}

        对于A,B有四种情况:
        [1]、m+n是偶数时,m是偶数,n是偶数
        [2]、m+n是偶数时,m是奇数,n是奇数
        [3]、m+n是奇数时,m是偶数,n是奇数
        [4]、m+n是偶数时,m是奇数,n是偶数
现在假设一种理想的情况,令i=m/2,j=(m+n)/2-i,且A[i]>=B[j-1],A[i-1]<=B[j],即left_A+left_B全部小于A[i]和B[j],由i与j的位置可知,对于[1]、[2] ,left_A+left_B=right_A+right_B,对于[3]、[4],left_A+left_B+1=right_A+right_B,因为中位数被放到了右侧的部分,此时
对于[1]、[2]两种情况,中位数
对于[3]、[4]两种情况,中位数
但实际上如此理想的情况基本不会存在,因此我们需要调整i,j的位置。
当A[i]<B[j-1]时,必然有A[i-1]<B[j],这时,需要增大i,即令i指向的位置向右移,在增大i的同时必然会导致j的减小,因为j=(m+n)/2-i,确保left_A+left_B包含A,B两个数组一半的元素(m+n是偶数时,若m+n是奇数,left_A+left_B+1=right_A+right_B)。我们可以令i=i+1,j=j-1。
当A[i-1]>B[j]时,必然有A[i]>B[j-1],这时,需要减小i。令i=i-1,j=j+1。
调整i,j的位置后,重新判断是否满足条件A[i]>=B[j-1],A[i-1]<=B[j]。
所以上述过程可以写作如下的形式,
1、if A[i]>=B[j-1],A[i-1]<=B[j]
        if m+n是偶数
                median=(max(A[i-1],B[j-1])+min(A[i],B[j]))/2
        if m+n是奇数
                min(A[i],B[j])
2、if A[i]<B[j-1]
        i=i+1,j=j-1
        重复1
3、if A[i-1]>B[j]
        i=i-1,j=j+1
        重复1

while (i>=0&&i<m) {
    if (i-1>=0){
        if (A[i]>=B[j-1]&&A[i-1]<=B[j]) {
            return (m+n)%2==0? 
		(Math.max(A[i-1],B[j-1])+Math.min(A[i],B[j]))/2.0
                    :Math.min(A[i],B[j]);
        } else if (A[i]<B[j-1]) {
            min = i;
            i=(max+min)/2;
            j=(m+n)/2-i;
        } else if (A[i-1]>B[j]) {
            max = i;
            i=(max+min)/2;
            j=(m+n)/2-i;
        }
    }
}

当然上面没有考虑边界的情况,即i=0和i=m-1时的情况,下面讨论边界的情况。
当i=0时,i-1会超出数组A的下界,这时我们只需判断A[i]>=B[j-1]是否成立,若有A[i]>=B[j-1]
if m+n是偶数
        median=(B[j-1]+min(A[i],B[j]))/2
if m+n是奇数
        median=min(A[i],B[j])

if (i==0) {
    if (A[i]>=B[j-1]) {
        return (m+n)%2==0? (B[j-1]+Math.min(A[i],B[j]))/2.0
                :Math.min(A[i],B[j]);
    }
}

  若A[i]> B[j],这时,i需要继续减小,i会小于0,出现这种情况时, A中的元素全部大于B中的元素。
  当i=m-1时,i-1不会超出下界,只需注意A[m-1]<=B[0]时的情况,当A[m-1]<=B[0]时,A中的元素全部小于B。
  我们需要注意j的值是否超过上界或者下界吗?
  当m<n时,不需要关注,因为这时j一定不会超出上界或者下界,只需要注意当m=n且A中元素全部大于或小于B时的情况,若j会超出上界。这种情况我们提前进行处理。

/**
 * 处理A全部大于B或A全部小于B的情况
 */
if (A[0]>=B[n-1]) { //A 全部属于right_part;
    if ((m+n)%2==0) { //m+n是偶数时
        if (m==n) { //max{right_part} = A[0],min{left_part} = B[n-1]
            return (A[0] + B[n - 1])/2.0;
        } else {
            return (B[(m+n)/2]+B[(m+n)/2-1])/2.0;
        }
    } else {
        return B[(m+n)/2];
    }
} else if (A[m-1]<=B[0]) { //A 全部属于left_part;
    if ((m+n)%2==0) { //m+n是偶数
        if (m==n) { //max{left_part} = A[m-1],min{right_part} = B[0]
            return (A[m-1] + B[0])/2.0;
        } else {
            return (B[(n-m)/2]+B[(n-m)/2-1])/2.0;
        }
    } else { //m+n是奇数
        return (B[(n-m)/2]);
    }
}

  同时,我们要注意到,要求时间复杂度为O(log(m + n)),所以在增大或者减小i时我们采用二分查找。我们知道二分查找的时间复杂度为O(log(2n)),采用二分查找的方法,时间复杂度为O(log(m))。

if (A[i]<B[j-1]) {
    min = i;
    i=(max+min)/2;
    j=(m+n)/2-i;
} else if (A[i-1]>B[j]) {
    max = i;
    i=(max+min)/2;
    j=(m+n)/2-i;
}

代码实现

private static double findMedianSortedArrays(int[] A, int[] B){
    /**
     * 确保A的长度小于等于B的长度
     */
    int m = A.length;
    int n = B.length;
    if (m > n) { // to ensure m<=n
        int[] temp = A;
        A = B;
        B = temp;
        int tmp = m;
        m = n;
        n = tmp;
    }
    /**
     * 处理A全部大于B或A全部小于B的情况
     */
    if (A[0]>=B[n-1]) { //A 全部属于right_part;
        if ((m+n)%2==0) { //m+n是偶数时
            if (m==n) { //max{right_part} = A[0],min{left_part} = B[n-1]
                return (A[0] + B[n - 1])/2.0;
            } else {
                return (B[(m+n)/2]+B[(m+n)/2-1])/2.0;
            }
        } else {
            return B[(m+n)/2];
        }
    } else if (A[m-1]<=B[0]) { //A 全部属于left_part;
        if ((m+n)%2==0) { //m+n是偶数
            if (m==n) { //max{left_part} = A[m-1],min{right_part} = B[0]
                return (A[m-1] + B[0])/2.0;
            } else {
                return (B[(n-m)/2]+B[(n-m)/2-1])/2.0;
            }
        } else { //m+n是奇数
            return (B[(n-m)/2]);
        }
    }

    int max = m;
    int min = 0;

    int i = (max+min)/2;
    int j = (m+n)/2-i;

    while (i>=0&&i<m) {

        if (i==0) {
            if (A[i]>=B[j-1]) {
                return (m+n)%2==0? (B[j-1]+Math.min(A[i],B[j]))/2.0
                        :Math.min(A[i],B[j]);
            }
        }

        if (i-1>=0){
            if (A[i]>=B[j-1]&&A[i-1]<=B[j]) {
                return (m+n)%2==0? (Math.max(A[i-1],B[j-1])+Math.min(A[i],B[j]))/2.0
                        :Math.min(A[i],B[j]);
            } else if (A[i]<B[j-1]) {
                min = i;
                i=(max+min)/2;
                j=(m+n)/2-i;
            } else if (A[i-1]>B[j]) {
                max = i;
                i=(max+min)/2;
                j=(m+n)/2-i;
            }
        }

    }

    return 0.0;

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值