[前端]_一起刷leetcode 4. 寻找两个正序数组的中位数

178 阅读4分钟

这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战

题目

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

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

算法的时间复杂度应该为 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
  • -106 <= nums1[i], nums2[i] <= 106

思路

常规做法我们可以用归并排序,来合并两个有序的数组,然后判断数组的长度,如果数组长度是奇数,我们就直接返回最中间的元素,如果数组长度是偶数,我们就返回中间两个元素的平均值即可。但是这道题要求我们时间复杂度压在O(log (m+n))内,说明不想让我们合并数组,得直接用二分查找去找出我们指定的位置

  1. 首先我们可以根据两个数组的长度来确定中位数的值的索引k
    • 如果两数组相加的和是奇数,说明中位数刚好是排在中间的一个数
    • 如果两数组相加的和是偶数,说明中位数是排在中间的两个数的平均值
  2. 然后我们可以对要查找的K个元素做一次二分查找,每次从两个数组中分别取出k/2个元素,比较它们最后一个元素的大小;
  3. 接着把小的那部分元素当作我们查找出来的第k/2小的元素,k每次减少一小半,记得如果k是7,只减去3
  4. 这样子递归执行直到k的值为1的时候,比较两个数组的第一个元素即可
  5. 当然,中间我们需要做特殊处理,如果一个数组的值不足这个k/2时候,我们可以把多余部分从另一个数组中直接减去即可。

举个例子:

我们要从两个数组[1, 3, 5, 7][2, 4, 6]中找到中位数

我们会先找到中位数是第几个元素: (4 + 3 >> 1) + 1 ==> 4

PS: 4 + 3 >> 1 等同于 Math.floor((4 + 3) / 2)

也就是我们要找到所有数据中排在第4大的元素,那么我们可以从两边各取两个元素

[1, 3][2, 4] 比较它们末尾元素的大小,因为 3 < 4, 所以在这两个升序数组中,前面的4个最小的数一定包含[1, 3]

接着我们只需要找出两个数组中前面2个最小的数即可,我们要找k个数, 那么我们每次就从数组中各自找出k / 2部分,假设一人出一半,然后把小的那一截放进去即可。

当然,如果一个数组是空的怎么办,我们可以在循环的时候加个判断,如果数据中的元素不够k / 2个,那么不够的那部分直接从另一个数组中取即可。

实现

/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number}
 */
function findMedianSortedArrays(nums1, nums2) {
    const m = nums1.length, n = nums2.length;
    
    // 实际元素比索引多1,所以要 +1
    const mid = (m + n >> 1) + 1;
    if ((m + n) % 2 === 1) {
        return findKthNum(nums1, nums2, mid);
    } else {
        return (findKthNum(nums1, nums2, mid) + findKthNum(nums1, nums2, mid - 1)) / 2;
    }
}

function findKthNum(nums1, nums2, k) {
   const m = nums1.length, n = nums2.length;
    let left1 = left2 = 0;

    // 一直二分查找,直到k为1的时候,比较两者的最小值即可
    while (k > 1 && left1 < m && left2 < n) {
        const mid = k >> 1;
        const index1 = left1 + mid - 1,
                index2 = left2 + mid - 1;
                
        // 如果两边值都够的话
        if (index1 < m && index2 < n) {
            if (nums1[index1] < nums2[index2]) {
                left1 = index1 + 1;
            } else {
                left2 = index2 + 1;
            }

            k -= mid;
        } else {
            // 把双方拉到同一起跑线再比较
            const min = Math.min(m - left1, n - left2);

            if (m - left1 === min) {
                left2 += k - 2 * min;
            } else {
                left1 += k - 2 * min;
            }

            k = 2 * min;
        }
    }

    // 如果有一个到尽头了
    if (left1 === m) {
        return nums2[left2 + k - 1];
    }

    if (left2 === n) {
        return nums1[left1 + k - 1];
    }

    // 返回两个数组的最小元素
    return Math.min(nums1[left1 + k - 1], nums2[left2 + k - 1]);
}

看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。