数据结构算法题/两个有序数组的中位数

有三种方法,时间复杂度分别是O(m+n) ,O(k),O(log(m+n))

注意点:
判断合并后的数组的元素个数是奇数还是偶数
如果是奇数取中间值;如果是偶数取中间2个数的平均值。

两种求中位数的方法:
(1)方法1,判断奇数个还是偶数个

if (lengthall % 2 == 0){
    result = ( all[lengthall/2-1] + all[lengthall/2] )*1.0/2;
}else{
    result = all[lengthall/2];
}
(2)方法2,不需要判断奇数个还是偶数个
        int l = (lengthall+1)/2-1;
        int r = (lengthall+2)/2-1;
        //上面减1的原因是坐标从0开始的
        result = (all[l] + all[r])*1.0/2;
        //这种求中位数的方法不需要判断是奇数还是偶数个

思考:

中位数相当于合并之后的top(n/2),如果是其他top数也是可以的。所以如果是一个无序数组的top k就只用小顶堆。如果是两个有序数组合并之后的top k就可以用如下的方法。

(1)O(m+n)合并两有序数组成一个新有序数组,再按中间位置取值。

两个有序数组合并为一个有序数组的时间复杂度O(m+n)。请参考https://blog.csdn.net/fkyyly/article/details/83145276

/**1
 * O(m+n)
 * 合并两有序数组成一个新有序数组,再按中间位置取值
 * @param nums1
 * @param nums2
 * @return
 */
public static double findMedianSortedArrays1(int[] nums1, int[] nums2) {
    int length1 = nums1.length;
    int length2 = nums2.length;
    int lengthall = length1 + length2;
    int[] all = new int[length1+length2];
    int i=0,j=0,k=0;
    while (i<length1 && j <length2){
        if(nums1[i]<nums2[j]){
            all[k]=nums1[i];
            i++;
            k++;
        }else{
            all[k] = nums2[j];
            j++;
            k++;
        }
    }
    while (i<length1){
        all[k]=nums1[i];
        i++;
        k++;
    }
    while (j<length2){
        all[k] = nums2[j];
        j++;
        k++;
    }
    double result;
    if (lengthall % 2 == 0){
        result = ( all[lengthall/2-1] + all[lengthall/2] )*1.0/2;
    }else{
        result = all[lengthall/2];
    }
    return result;
}

(2)O(k)两个指针分别从两数组开头指,比较两指针处的数,谁小谁往后移,直到两指针共移动k次,k为中间位置

/**2
 * O(k)
 * 两个指针分别从两数组开头指,比较两指针处的数,谁小谁往后移,
 * 直到两指针共移动k次,k为中间位置
 * 注意总数有奇数个还是偶数个计算中位数的方法
 * @param nums1
 * @param nums2
 * @return
 */
public static double findMedianSortedArrays2(int[] nums1, int[] nums2) {
    int length1 = nums1.length;
    int length2 = nums2.length;
    int lengthall = length1 + length2;

    int inx1 = 0;
    int inx2 = 0;
    int x = -1;
    int y = -1;

    while (inx1 + inx2 < lengthall){
        if (inx1 < length1) {
            while (inx2 == length2 ||  nums1[inx1] <= nums2[inx2] ) {
            //nums1到头了或者nums1此时的元素小于nums2此时的元素,这两种情况下从nums1取值(也就是nums1和nums2小的移动)
                inx1++;
                //下面是总长度是奇数还是偶数对于中位数的计算
                if (inx1 + inx2 == (lengthall + 1) / 2) {
                    x = nums1[inx1 - 1];
                }
                if (inx1 + inx2 == (lengthall + 2) / 2) {
                    y = nums1[inx1 - 1];
                    return (x + y) * 1.0 / 2;
                }
                if (inx1 == length1){
                    break;
                }
            }
        }
        if (inx2 < length2){
            while ( inx1 == length1 || nums2[inx2] <= nums1[inx1] ) {
                inx2++;
                if (inx1 + inx2 == (lengthall + 1)/2){
                    x = nums2[inx2-1];
                }
                if (inx1 + inx2 == (lengthall + 2)/2){
                    y = nums2[inx2-1];
                    return (x+y)*1.0/2;
                }
                if (inx2 == length2){
                    break;
                }
            }
        }
    }
    return -1;
}

(3)O(log(m+n))两个数组分别采用二分法查找

思想:上面的是数组num1[],下面是数组num2[]。

使用递归的思想分别对num1和num2求中位数,因为是有序的,所以直接取n/2处的值即可。

(1)如果num1的中位数=num2的中位数,那么就是最终的结果。

(2)如果num1的中位数<num2的中位数,那么下次就在[B,C]和[D,E]两个新数组直接找中位数。

(3)如果num1的中位数>num2的中位数,那么下次就在[A,B]和[E,F]两个新数组直接找中位数。

(4)重复这个步骤,直到新数组的长度为2

如果数组a的中位数小于数组b的中位数,那么整体的中位数只可能出现在a的右区间加上b的左区间之中; 
如果数组a的中位数大于等于数组b的中位数,那么整体的中位数只可能出现在a的左区间加上b的右区间之中。 

关键就是利用分治的思想逐渐缩小a的区间和b的区间来找到中位数。

首先假设数组A和B的元素个数都大于k/2,我们比较A[k/2-1]和B[k/2-1]两个元素,这两个元素分别表示A的第k/2小的元素和B的第k/2小的元素。这两个元素比较共有三种情况:>、<和=。如果A[k/2-1]<B[k/2-1],这表示A[0]到A[k/2-1]的元素都在A和B合并之后的前k小的元素中。换句话说,A[k/2-1]不可能大于两数组合并之后的第k小值,所以我们可以将其抛弃。


当A[k/2-1]>B[k/2-1]时存在类似的结论。

当A[k/2-1]=B[k/2-1]时,我们已经找到了第k小的数,也即这个相等的元素,我们将其记为m。由于在A和B中分别有k/2-1个元素小于m,所以m即是第k小的数。(这里可能有人会有疑问,如果k为奇数,则m不是中位数。这里是进行了理想化考虑,在实际代码中略有不同,是先求k/2,然后利用k-k/2获得另一个数。)

通过上面的分析,我们即可以采用递归的方式实现寻找第k小的数。此外我们还需要考虑几个边界条件:

如果A或者B为空,则直接返回B[k-1]或者A[k-1];
如果k为1,我们只需要返回A[0]和B[0]中的较小值;
如果A[k/2-1]=B[k/2-1],返回其中一个;

https://blog.csdn.net/hjhjhx26364/article/details/80251675 
https://blog.csdn.net/darminz/article/details/77500474

猜你喜欢

转载自blog.csdn.net/fkyyly/article/details/83143577