[leetcode] 4.寻找两个正序数组的中位数

文章目录

  • 题目描述
  • 解题方法
    • 方法一:双指针遍历
      • java代码
    • 方法二:二分查找
      • java代码

题目描述

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

算法的时间复杂度应该为 O ( l o g ( m + n ) ) O(log (m+n)) O(log(m+n))

示例 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

提示:
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
− 1 0 6 -10^6 106 <= nums1[i], nums2[i] <= 1 0 6 10^6 106

解题方法

方法一:双指针遍历

由于题目是有序数组,所以可以采用双指针遍历的方法。 l 1 l1 l1代表 n u m s 1 nums1 nums1数组指针索引, l 2 l2 l2代表 n u m s 2 nums2 nums2数组指针索引, m i d I n d e x midIndex midIndex代表中位数索引位置,当 n u m s [ l 1 ] nums[l1] nums[l1]<= n u m s [ l 2 ] nums[l2] nums[l2] l 1 l1 l1往右移动;当 n u m s [ l 1 ] nums[l1] nums[l1]> n u m s [ l 2 ] nums[l2] nums[l2] l 2 l2 l2往右移动;直到指针移动次数移动到中位数索引位置时,取 n u m s [ l 1 ] nums[l1] nums[l1] n u m s [ l 2 ] nums[l2] nums[l2]中的最小值即为中位数。

java代码

public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    int length1 = nums1.length;
    int length2 = nums2.length;
    int totalLength = length1 + length2;
    if (totalLength % 2 == 1) {
        int midIndex = totalLength / 2;
        return findMedianNumber(nums1, nums2, midIndex);
    } else {
        int midIndex1 = totalLength / 2 - 1;
        int midIndex2 = totalLength / 2;
        double mediation = (findMedianNumber(nums1, nums2, midIndex1) + findMedianNumber(nums1, nums2, midIndex2)) / 2.0;
        return mediation;
    }
}

public double findMedianNumber(int[] nums1, int[] nums2, int midIndex) {
    int length1 = nums1.length;
    int length2 = nums2.length;
    int l1 = 0;
    int l2 = 0;
    int index = 0;
    while(l1 < length1 && l2 < length2) {
        if(index == midIndex) {
            return Math.min(nums1[l1], nums2[l2]);
        }
        if(nums1[l1] <= nums2[l2]) {
            l1++;
        } else {
            l2++;
        }
        index++;
    }
    if(l1 == length1) {
        return nums2[l2 + midIndex - index];
    } else {
        return nums1[l1 + midIndex - index];
    }
}

时间复杂度: O ( M + N ) O(M+N) O(M+N)
空间复杂度: O ( 1 ) O(1) O(1)

方法二:二分查找

可以看出,方法一中,我们寻找中位数时,是通过两个数组从左到右依次遍历的方式累加查找次数,寻找中位数,这样我们总共需要查找(m + n) / 2 + 1次,那么有没有更快的方式缩短查找次数呢?答案是每次采用二分查找,排除更多的元素,进一步缩短查找次数。

寻找中位数,我们可以理解为当两个数组长度之和为奇数时,查找第(m + n) / 2 + 1大的元素;或者当两个数组长度之和为偶数时,查找第(m + n) / 2 大的元素和(m + n) / 2 + 1大的元素,然后取平均值。这样题目就可以转化为寻找数组中第k小的数。

假设两个有序数组分别是 A A A B B B。要找到第 k 个元素,我们可以比较 A [ k / 2 − 1 ] A[k/2−1] A[k/21] B [ k / 2 − 1 ] B[k/2−1] B[k/21]。由于 A [ k / 2 − 1 ] A[k/2−1] A[k/21] B [ k / 2 − 1 ] B[k/2−1] B[k/21]的前面分别有 A [ 0   . .   k / 2 − 2 ] A[0 .. k/2−2] A[0 .. k/22] B [ 0   . .   k / 2 − 2 ] B[0 .. k/2−2] B[0 .. k/22],即 k / 2 − 1 k/2−1 k/21个元素,对于 A [ k / 2 − 1 ] A[k/2−1] A[k/21] B [ k / 2 − 1 ] B[k/2−1] B[k/21] 中的较小值,最多只会有 ( k / 2 − 1 ) + ( k / 2 − 1 ) = k − 2 (k/2−1)+(k/2−1) = k−2 (k/21)+(k/21)=k2 个元素比它小,则 A [ k / 2 − 1 ] A[k/2−1] A[k/21] B [ k / 2 − 1 ] B[k/2−1] B[k/21]中的较小值最大为第 k − 1 k-1 k1小的数,那么它就不是第 k k k小的数了。

因此我们可以归纳出二种情况:

  • 如果 A [ k / 2 − 1 ] < = B [ k / 2 − 1 ] A[k/2−1]<=B[k/2−1] A[k/21]<=B[k/21],则比 A [ k / 2 − 1 ] A[k/2−1] A[k/21]小的数最多只有 A A A的前 k / 2 − 1 k/2−1 k/21个数和 B B B 的前 k / 2 − 1 k/2−1 k/21 个数,即比 A [ k / 2 − 1 ] A[k/2−1] A[k/21] 小的数最多只有 k − 2 k−2 k2 个,因此 A [ k / 2 − 1 ] A[k/2−1] A[k/21]最大是第 k − 1 k-1 k1小的数,不可能是第 k k k个数, A [ 0 ] A[0] A[0] A [ k / 2 − 1 ] A[k/2−1] A[k/21]也都不可能是第 k k k 个数,可以全部排除。

  • 如果 A [ k / 2 − 1 ] > B [ k / 2 − 1 ] A[k/2−1]>B[k/2−1] A[k/21]>B[k/21],则可以排除 B [ 0 ] B[0] B[0] B [ k / 2 − 1 ] B[k/2−1] B[k/21]的元素。

可以看到,比较 A [ k / 2 − 1 ] A[k/2−1] A[k/21] B [ k / 2 − 1 ] B[k/2−1] B[k/21] 之后,可以排除 k / 2 k/2 k/2个不可能是第 k k k小的数,查找范围缩小了一半。同时,我们将在排除后的新数组上继续进行二分查找,并且根据我们排除数的个数,减少 k k k 的值,这是因为我们排除的数都不大于第 k k k小的数。当 k k k的值减小为1时,取 A A A B B B数组当前索引位置的较小值即为第k小的数。

java代码

public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    int length1 = nums1.length;
    int length2 = nums2.length;
    int totalLength = length1 + length2;
    if (totalLength % 2 == 1) {
        int midIndex = totalLength / 2;
        double median = getKthElement(nums1, nums2, midIndex + 1);
        return median;
    } else {
        int midIndex1 = totalLength / 2 - 1;
        int midIndex2 = totalLength / 2;
        double median = (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0;
        return median;
    }
}

public int getKthElement(int[] nums1, int[] nums2, int k) {
    /* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
     * 这里的 "/" 表示整除
     * nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
     * nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
     * 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
     * 这样 pivot 本身最大也只能是第 k-1 小的元素
     * 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组
     * 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组
     * 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
     */

    int length1 = nums1.length;
    int length2 = nums2.length;
    int index1 = 0;
    int index2 = 0;

    while (true) {
        // 边界情况
        if (index1 == length1) {
            return nums2[index2 + k - 1];
        }
        if (index2 == length2) {
            return nums1[index1 + k - 1];
        }
        if (k == 1) {
            return Math.min(nums1[index1], nums2[index2]);
        }

        // 正常情况
        int half = k / 2;
        int newIndex1 = Math.min(index1 + half, length1) - 1;
        int newIndex2 = Math.min(index2 + half, length2) - 1;
        int pivot1 = nums1[newIndex1];
        int pivot2 = nums2[newIndex2];
        if (pivot1 <= pivot2) {
            k -= (newIndex1 - index1 + 1);
            index1 = newIndex1 + 1;
        } else {
            k -= (newIndex2 - index2 + 1);
            index2 = newIndex2 + 1;
        }
    }
}

时间复杂度: O ( l o g ( M + N ) ) O(log (M+N)) O(log(M+N))
空间复杂度: O ( 1 ) O(1) O(1)


  • 个人公众号
    [leetcode] 4.寻找两个正序数组的中位数_第1张图片
  • 个人小游戏
    [leetcode] 4.寻找两个正序数组的中位数_第2张图片

你可能感兴趣的:(leetcode,算法,java,数据结构)