算法-两个排序数组的中位数

题目

两个排序数组的中位数

给定两个大小为 m 和 n 的有序数组 nums1 和 nums2 。

请找出这两个有序数组的中位数。要求算法的时间复杂度为 O(log (m+n)) 。

示例 1:

nums1 = [1, 3]
nums2 = [2]

中位数是 2.0
示例 2:

nums1 = [1, 2]
nums2 = [3, 4]

中位数是 (2 + 3)/2 = 2.5
复制代码
思路:
  1. 中位数下标 (假设数组为arr,长度为len,下标范围[0 ,len - 1])
    1. 如果len % 2 = 1 (为奇数),那么中位数的值为arr[mindIndex = len /2] 。
    2. 如果len % 2 - 0 (为偶数),那么中位数的值为(arr[len/2]+arr[len/2 - 1])/2.0的值的和的平均值。
  2. 思路一:最简单的思路把两个有序数组合并成一个有序的数组,再通过规则1.获取中位数的值。优化:结合题目要求,只需要获取中位数的值,并不需要真的要把两个数组排序成一个有序数组。那么通过两数组间隔读取,下标后移,直到累加到位置为为len/2即可获取到结果。这里如何更快地把找到len/2的位置是效率的关键。
  3. 思路二:假定数组A、B、长度分别为Alen、Blen。那么排序后的新数组为C、Clen。Clen = (Alen + Blen + 1)/ 2。因为A和B都是有序的,如果C是由A和B的一部分元素组成的。那么可以想象,C必然是A左边一定长度为ALefPairLen和B的左边一定长度为BLeftPairLen的两部分组成.ALefPairLen和BLeftPairLen是联动关系,ALefPairLen + BLeftPairLen = (Alen + Blen + 1)/ 2,一增一减,一减一增,总长度是固定的。然后为了获得中位数,只需要获取其中一个数组的用于合并那部分的末尾Index,或者index + 1 (index + 1 = LefPairLen) ;这就变成了一个查找指定下标的问题了。
继续思路二(切换到程序中的变量,便于理解)
  1. 定义: 假定,A数组的用于合并的长度为i,,那么最后一个元素的下标为i-1,i为用于合并的最后一个元素的下一个元素的下标。B的数组的长度j = (Alen + Blen + 1)/ 2 - i,那么B数组的最后一个元素的下标为j-1,j为用于合并的最后一个元素的下一个元素的下标。

  2. 结束条件: 要获取合法的i值,需要满足 A[i -1 ] <= B[j] && A[j] >= B[j -1]; 如果leftPairA的最后一个元素少于leftPairB在原数组中最后一个元素的下一个元素,那么leftPairA是符合要求的。反过来B数组也一样。要达到在leftPairA和leftPairB右边的元素都比这两部分大,不然就需要继续调整。

  3. 特殊情况:代码注释中记录。

  4. 数的获取: 因为取长度的时候为halfLen = (Alen + Blen + 1)/ 2。如果总长度为奇数,最后一位就是中位fmax(a[i-1],B[j-1])。如果总长度为偶数,需要取处于中间的两个数,最后一位(fmax(a[i-1],B[j-1])为左边的一个,右边一个为halfLen之外的fmin(A[i],B[j]))。

double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) {
    int* A = nums1;
    int* B = nums2;
    int m = nums1Size;
    int n = nums2Size;
    
    if (m > n ) {
        int* tmp = A;
        A = B;
        B = tmp;
        int k = m;
        m = n;
        n = k;
    }
    
    int iMin = 0;
    int iMax = m;
    //halfLen 偶数等于实际的一半,奇数等于向上取整
    int halfLen = (m + n + 1) / 2 ;
    
    while (iMin <= iMax) {
        //偶数index[i-1 , i ],奇数[i]
        int i = (iMin + iMax) /2;
        //长度之外的第一个元素
        int j = halfLen - i;
        if (i < iMax && A[i] < B[j - 1] )
        {
            //太小了,下限提高
            iMin = iMin + 1;
        }
        else if ( i > iMin && A[i- 1] > B[j]) {
            //太大了,上限降低
            iMax = iMax - 1;
        }
        else
        {
            
            //满足需求 (A[i - 1] < B[j] && A[i] > B[j - 1]) || i == 0 || j == 0 || i == m || j == n
            int leftMax = 0;
            //m > n
            if (i == 0) {
                //在A被选中的长度为0
                leftMax = B[j - 1];
            }else if (j == 0)
            {
                //在B中被选中的长度为0
                leftMax = A[i - 1];
            }
            else
            {
                //i = m;代表A数组都被选中
                //j = n;代表整个B数组都被选中
                //包含 j == n || i == m 的情况
                leftMax = fmax(A[i - 1], B[j - 1]);
            }
            
            if ((m + n) % 2 == 1) {
                return leftMax;
            }
            
            int rightMin = 0;
            if (i == m)
            {
                //A全部被选中。那么总长度为偶数时,中位数为中间两数的平均值,选中的最后一个数为leftMax,rightMin为hanlflen之外的第一个数B[j];
                rightMin = B[j];
            }else if (j == n)
            {
                //B全部被选中。那么总长度为偶数时,中位数为中间两数的平均值,选中的最后一个数为leftMax,rightMin为hanlflen之外的第一个数A[j];
                rightMin = A[i];
            }
            else
            {
                //包含j == 0 || i == 0 的情况
                rightMin = fmin(A[i], B[j]);
            }
            
            return (leftMax + rightMin) / 2.0 ;
        }
    }
    
    return 0;
}

复制代码
//另外一种效率不错的解法
double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) {
    int* A = nums1;
    int* B = nums2;
    int m = nums1Size;
    int n = nums2Size;
    
    if (m < n ) {
        int* tmp = A;
        A = B;
        B = tmp;
        int k = m;
        m = n;
        n = k;
    }
    
    int iMin = 0;
    int iMax = m;
    int halfLen = (m + n + 1) / 2 ;
    
    while (iMin <= iMax) {
        //偶数index[i-1 , i ],奇数[i]
        int i = (iMin + iMax) /2;
        //长度之外的第一个元素
        int j = halfLen - i;
        if ( A[i- 1] > B[j] && i > iMin ) {
            //太大了,上限降低
            iMax = iMax - 1;
        }
        else if (A[i] < B[j - 1] && i < iMax)
        {
            //太小了,下限提高
            iMin = iMin + 1;
        }
        else
        {
            
            //满足需求 A[i - 1] < B[j] && A[i] > B[j - 1]
            int leftMax = 0;
            //m > n
            if (i == 0) {
                //在A中无法找到符合的数值
                leftMax = B[j - 1];
            }else if (j == 0)
            {
                //在B中无法找到符合的数值
                leftMax = A[i - 1];
            }
            else
            {
                //包含 j == n || i == m 的情况
                leftMax = fmax(A[i - 1], B[j - 1]);
                if ((m + n) % 2 == 1) {
                    return leftMax;
                }
            }
            
            int rightMin = 0;
             if (i == m)
            {
                rightMin = B[j];
            }else if (j == n)
            {
                rightMin = A[i];
            }
            else
            {
                //包含j == 0 || i == 0 的情况
                rightMin = fmin(A[i], B[j]);
        
            
            return (leftMax + rightMin) / 2.0 ;
        }
    }
    return 0;
}

复制代码

小结:这道理题要处理的细节比较多。切割长度halfLen = (m + n + 1) / 2,最后一位是哪一位,是刚好是中位数呢,还是中位数左移一位,还是右移一位呢。这个会影响到后面的中位数下标取值。还要搞懂每个临界条件代表的意义。总的来说如果每一句代码都是必须的,那么必然要做到对代码的含义了然于心,才能保证程序的正确性。对的思路+对的编码 = 对的答案。

待完善L('ω')┘三└('ω')」....

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值