• 如何找到两个升序数组归并后的升序数组的中位数


    一、什么是中位数:

      当归并后的大数组的长度是奇数,中位数就是位于正中的元素;

      当归并后的大数组的长度是偶数,中位数就是位于正中的两个元素的平均值;

    二、普通做法:

      直接把两个数组进行归并操作,根据归并后数组长度求中位数;

      时间复杂度和空间复杂度都是 O(m+n),m、n分别是两个数组长度;

    三、一个升序数组的中位数特点:

      1、中位数把一个升序数组分成了长度相等的两部分,其中左半部分的最大值永远小于右半部分的最小值。

        

      如上图所示,对于偶数长度的数组,可以根据中位数分成长度相等的两部分,左半部分最大元素(6),永远小于等于右半部分的最小元素(7)。

      对于奇数长度的数组,同样可以根据中位数分成两部分: 

        

      如上图所示,对于奇数长度的数组,如果把中位数本身归入左半部分,则左半边长度 = 右半边长度+1。

         左半部分最大元素(5),永远小于等于右半部分的最小元素(6)。

      2、大数组的左右两部分,分别来源于两个初始数组A和B的左右部分。

         什么意思呢?大数组被中位数等分的左右两部分,每一部分根据来源又可以再划分成两部分,其中一部分来自数组A的元素,另一部分来自数组B的元素:

         

      如图所示,原始数组A和B,各自分成绿色和橙色两部分。其中数值较小的绿色元素组成了大数组的左半部分,数值较大的橙色元素组成了大数组的右半部分。

      最重要的是,绿色元素和橙色元素的数量是相等的(偶数情况),而且最大的绿色元素小于等于最小的橙色元素。

    四、根据特点得出的条件:

      假设数组A的长度是m,绿色和橙色元素的分界点是i,数组B的长度是n,绿色和橙色元素的分界点是j,那么为了让大数组的左右两部分长度相等,则i和j需要符合如下两个条件:

        i + j = (m+n+1)/2  (之所以m+n后面要再加1,是为了应对大数组长度为奇数的情况)

        Max(A[i-1],B[j-1]) <= Min(A[i], B[j])  (直白的说,就是最大的绿色元素小于等于最小的橙色元素)

    五、算法分析:

      由于m+n的值是恒定的,所以我们只要确定一个合适的i,就可以确定j,从而找到大数组左半部分和右半部分的分界,也就找到了归并之后大数组的中位数。

      如何找到合适的i值

        利用【二分查找】的思想。如何利用二分查找来确定i值呢?通过具体事例,让我们来演示一下:

          

        第一步,就像二分查找那样,把i设在数组A的正中位置,也就是让i=3

          

        第二步,根据i的值来确定j的值,j=(m+n+1)/2 - i =3

           

        第三步,验证i和j,分为下面三种情况:      

          1.B[j−1]≤A[i] && A[i−1]≤B[j]

            说明i和j左侧的元素都小于等于右侧,这一组i和j是我们想要的。

          2.A[i]<B[j−1]

            说明i对应的元素偏小了,i应该向右侧移动。

          3.A[i−1]>B[j]

            说明i-1对应的元素偏大了,i应该向左侧移动。

        显然,对于图中例子,属于情况2,A[3] < B[2],所以i应该向右移动。

        第四步,在数组A的右半部分,重新确定i的位置,就像二分查找一样

          

        第五步,同第二步,根据i的值来确定j的值,j=(m+n+1)/2 - i =1

          

        第六步,同第三步,验证i和j

           由于A[5] >= B[0]且B[1]>=A[4],所以这一组i和j是合适的!

         第七步,找出中位数 

          如果大数组长度是奇数,那么:

            中位数 = Max(A[i-1],B[j-1])  (也就是大数组左半部分的最大值)       

           如果大数组长度是偶数,那么:

            中位数 = (Max(A[i-1],B[j-1]) + Min(A[i], B[i]))/2  (也就是大数组左半部分的最大值和大数组右半部分的最小值取平均)

         在本例中,大数组长度是奇数,所以中位数=Max(8,1) = 8

     六、特殊情况:

      1.数组A的长度远大于数组B

        

        也就是m远大于n,这时候会出现什么问题呢?

          当我们设定了i的初值,也就是数组A正中间的元素,再计算j的时候有可能发生数组越界。

          因此,我们可以提前把数组A和B进行交换,较短的数组放在前面,i从较短的数组中取。这样做还有一个好处,由于数组A是较短数组,i的搜索次数减少了。

       2.无法找到合适的i值

         什么情况下会无法找到合适的i值呢?有两种情况:

          数组A的长度小于数组B,并且数组A的所有元素都大于数组B。

             

            这种情况下,无法通过二分查找寻找到符合B[j−1]≤A[i] && A[i−1]≤B[j]的i值,一直到i=0为止。

            此时我们可以跳出二分查找的循环,所求的中位数是B[j-1]。(仅奇数情况)

           数组A的长度小于数组B,并且数组A的所有元素都小于数组B。

             

            这种情况下,同样无法通过二分查找寻找到符合B[j−1]≤A[i] && A[i−1]≤B[j]的i值,一直到i=(数组A长度-1)为止。

             此时我们可以跳出二分查找的循环,所求的中位数是Max(A[i-1],B[j-1])(仅奇数情况)

     七、代码实现:

      

    public static double findMedianSortedArrays(int[] arrayA, int[]  arrayB) {
        int m = arrayA.length;
        int n = arrayB.length;
        //如果数组A的长度大于等于数组B,则交换数组
        if (m > n) {
            int[] temp = arrayA;
            arrayA = arrayB;
            arrayB = temp;
            int tmp = m;
            m = n;
            n = tmp;
        }
        int start = 0;
        int end = m;
        int mid = (m + n + 1) / 2;
        while (start <= end) {
            int i = (start + end) / 2;
            int j = mid - i;
            if (i < end && arrayB[j-1] > arrayA[i]){
                //i偏小了,需要右移
                start = i + 1;
            }
            else if (i > start && arrayA[i-1] > arrayB[j]) {
                //i偏大了,需要左移
                end = i - 1;
            }
            else {
                //i刚好合适,或i已达到数组边界
                int maxLeft;
                if (i == 0) {
                    maxLeft = arrayB[j-1];
                } else if (j == 0) {
                    maxLeft = arrayA[i-1];
                } else {
                    maxLeft = Math.max(arrayA[i-1], arrayB[j-1]);
                }
                if ( (m + n) % 2 == 1 ) {
                    //如果大数组的长度是奇数,中位数就是左半部分的最大值
                    return maxLeft;
                }
                int minRight;
                if (i == m) {
                    minRight = arrayB[j];
                } else if (j == n) {
                    minRight = arrayA[i];
                } else {
                    minRight = Math.min(arrayB[j], arrayA[i]);
                }
                //如果大数组的长度是偶数,取左侧最大值和右侧最小值的平均
                return (maxLeft + minRight) / 2.0;
            }
        }
        return 0.0;
    }
    
    public static void main(String[] args) {
        int[] arrayB = new int[]{3,5,6,7,8,12,20};
        int[] arrayA = new int[]{1,10,17,18};
        System.out.println(findMedianSortedArrays(arrayA, arrayB));
    }

     八、算法时间复杂度:

      由于最初的交换,数组A的长度是m、n中的最小值,而确定i的过程类似二分查找,所以时间复杂度是O(log min(m,n))。

  • 相关阅读:
    [BZOJ4199][NOI2015]品酒大会
    [BZOJ4198][Noi2015]荷马史诗
    [BZOJ4197][Noi2015]寿司晚宴
    [BZOJ4196][NOI2015]软件包管理器
    2016-11-15NOIP模拟赛
    2016.6.30模拟赛
    BZOJ3672: [Noi2014]购票
    UOJ#191. 【集训队互测2016】Unknown
    第四届CCF软件能力认证(CSP2015) 第五题(最小花费)题解
    bzoj3926: [Zjoi2015]诸神眷顾的幻想乡 对[广义后缀自动机]的一些理解
  • 原文地址:https://www.cnblogs.com/HF-Made/p/11382174.html
Copyright © 2020-2023  润新知