寻找两个正序数组的中位数

126 阅读2分钟

这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战

1.leetcode寻找两个正序数组的中位数

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

示例 1:

输入: nums1 = [1,3], nums2 = [2]
输出: 2.00000
解释: 合并数组 = [1,2,3] ,中位数 2

示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

示例 3:

输入: nums1 = [], nums2 = [1]
输出: 1.00000

解法1

暴力循环的方式,相当于将两个数组从小到大合并,可以不用真的合并为一个新数组,直接从最小的元素开始遍历依次取出两个数组中最小的元素,直到找到中间数就可以了,注意要考虑奇偶问题,如果长度len是奇数直接取总长度中间的len/2下标的数就可以了(下标从0开始),如果是偶数则需要取(len/2)-1和len/2下标的数相加除2。先获取两个数组的总长度,然后根据总长度可以得到是否奇偶。定义两个下标,用来获取两个数组的元素。循环的话只需要循环到总长度的一半就好了。先根据两个数组的下班取出元素,如果数组1的下标超出了边界,直接获取数组2的值,否则在判断数组2下标的边界,然后取出元素比较大小。获取当前的顺序元素值,直到遍历到倒数第二个元素时要判断是否偶数,是的话就需要带上这个数,否则直接获取最后一个元素值。最后判断奇偶返回结果。

public double findMedianSortedArrays000000000(int[] nums1, int[] nums2) {
    // 总长度
    int totalLength = nums1.length + nums2.length;
    // 是否偶数
    boolean isEvenNumber = totalLength % 2 == 0;

    // 两个数组的下标
    int index1 = 0;
    int index2 = 0;

    // 中位数,总长度偶数的有两个
    double sum = 0D;

    // 只用循环查找前半部分
    int len = totalLength / 2;
    // 取哪个数组的元素,从小到大
    boolean first;
    // 当前元素值
    int currentElement;
    for (int i = 0; i <= len; i++) {
        // 取出数组1和数组2的元素比较 取出两个数组中小的数
        first = index1 < nums1.length && (index2 == nums2.length || nums1[index1] < nums2[index2]);
        currentElement = first ? nums1[index1++] : nums2[index2++];

        // 是偶数 还是倒数第二个元素 要累加
        if (isEvenNumber && i == (len - 1)) {
            sum += currentElement;
        } else if (i == len) {
            sum += currentElement;
        }
    }

    return isEvenNumber ? sum / 2 : sum;
}

解法2

用两个指针m、n指向两个数组初始位置,每次折半查找,首先判断如果一个数组已经没有了,它的指针的位置已经比数组长度大时,说明这个数组的所以元素都被淘汰了,可以理解就剩一个数组了这时我们只需要到另外一个数组直接定位到目标位置k+指针-1,返回该值就可以了。如果k为1时,这时只用比较数组1和数组2的指针位置的元素就行了。否则需要取除两个数组中k/2分割位置的元素值来比较,哪个元素值小,就说明该数组指针位置前的元素不符合要求,可以被淘汰,讲指针往后移动,然后再继续折半查找直到找到为止。


public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    int len = nums1.length + nums2.length;
    // 不管奇偶,直接获取m+n+1/2 和m+n+2/2 的和再除2
    return (diGui(nums1, 0, nums2, 0, (len + 1) / 2) + diGui(nums1, 0, nums2, 0, (len + 2) / 2)) / 2D;
}

public int diGui(int[] nums1, int m, int[] nums2, int n, int k) {
    // 如果一个数组排除完了 那就直接找到另外一个数组的指定地方就行了
    if (m >= nums1.length) {
        return nums2[n + k - 1];
    }
    if (n >= nums2.length) {
        return nums1[m + k - 1];
    }
    // 直接比较两个数组的第一个元素(当前指针的位置)
    if (k == 1) {
        return Math.min(nums1[m], nums2[n]);
    }
    // 取出数组当前指针下的元素,如果没有元素了,就当作是最大的元素
    int v1 = (k / 2 + m - 1 < nums1.length) ? nums1[k / 2 + m - 1] : Integer.MAX_VALUE;
    int v2 = (k / 2 + n - 1 < nums2.length) ? nums2[k / 2 + n - 1] : Integer.MAX_VALUE;
    // k-k/2每次减半查找
    if (v1 < v2) {
        // nums1数组的小 所以排除nums1在k/2前的元素
        return diGui(nums1, m + k / 2, nums2, n, k - k / 2);
    } else {
        // nums2数组的小,所以排除nums2在k/2前的元素
        return diGui(nums1, m, nums2, n + k / 2, k - k / 2);
    }
}