leetcode力扣hot100困难题--4.俩个正序数列的中位数

题目

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

算法的时间复杂度应该为 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

思路解析

我们发现题目给的数据,俩个数组都是有序的,我们要找中位数,那么就转换为给定一个函数,找第k大的数问题。

那么会发现,实际上,我们num1和num2,在起始状态下,我们都去找它们的第k/2大的数

假定num1、num2数组的k/2的数如果为val1和val2,那么此时,如果val1的值如果<=val2的值,那么val1的前面k/2个值都不用看了他们肯定可以排除掉。

因为我们要找的是第k个值,k/2,向下取整,那么num1的前面k/2个值如果<=num2的第k/2个值,那么肯定是<=我们第k个值的,这个时候排除后,我们就可以用k减去我们排除掉的元素的个数

相似的,我们以此类推,知道最后num1的值或者num2的值都排除或者k为1的时候,那就可以很容易找到我们需要的值了。

例子

核心思路出现:"一次扔掉一大块"

我们能不能一次扔掉很多数?

假设:

复制代码

nums1: [ 1, 3, 5, 7, 9 ] nums2: [ 2, 4, 6, 8, 10 ]

我们要找:

第 7 小的数


我们做一件事(非常关键)

各自看第 k/2 个数

  • k = 7 → k/2 = 3

nums1 第 3 个是 5 nums2 第 3 个是 6


比较这两个数

5 < 6

现在问你一个问题:

nums1 里 [1,3,5] 有没有可能是第 7 小?

答案:不可能

为什么?

  • [1,3,5] 已经只有 3 个

  • 即使把 nums2 里比它们小的全算上

  • 它们最多也排在前面

  • 永远排不到第 7


重要结论

如果
nums1[k/2] <= nums2[k/2]

那么
nums1 的前 k/2 个元素一定不可能是第 k 小

可以 直接删掉


六、删掉之后发生了什么?

我们删掉了:

nums1: [1, 3, 5]

现在:

  • 已经排除了 3 个最小的

  • 第 7 小 → 变成 第 4 小

k = 7 - 3 = 4

数组状态变成:

nums1: [7, 9] nums2: [2, 4, 6, 8, 10]


七、继续重复这个过程

第 2 轮

  • k = 4 → k/2 = 2

  • nums1 第 2 个:9

  • nums2 第 2 个:4

9 > 4

删除 nums2 的前 2 个:

[2, 4]

更新:k = 4 - 2 = 2


第 3 轮

  • k = 2 → k/2 = 1

  • 比较当前最小的两个数:

7 vs 6

删除 6

k = 1


k == 1 是什么?

当前两个数组中最小的那个数

复制代码

min(7, 8) = 7

这就是第 7 小的数


八、为什么这个方法快?

每一轮都在干嘛?

  • 删除 k/2 个元素

  • k 直接砍一半

k → k/2 → k/4 → k/8 ...

这是 对数级别


九、为什么不会删错?(最重要的问题)

因为:

  • 两个数组是 有序的

  • 每次比较的是「第 k/2 个」

  • 被删掉的那一边:

    • 数量 + 排名上限

    • 永远不可能达到第 k

这是一个 严格数学保证


十、把它和中位数连起来

中位数 = 第 k 小

  • 奇数长度 → 找一个 k

  • 偶数长度 → 找两个 k,求平均

所以整个题的本质就是:

在两个有序数组中找第 k 小的数

代码实现

java 复制代码
class Solution {

    /**
     * 主函数:在两个有序数组中寻找中位数
     */
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {

        int length1 = nums1.length;
        int length2 = nums2.length;

        // 总元素个数
        int n = length1 + length2;

        // 如果总长度是奇数
        // 中位数就是第 (n/2 + 1) 小的元素(注意:k 从 1 开始)
        if (n % 2 == 1) {
            return getKthElement(nums1, nums2, n / 2 + 1);
        }
        // 如果总长度是偶数
        // 中位数是第 n/2 小 和 第 n/2 + 1 小 的平均值
        else {
            return (getKthElement(nums1, nums2, n / 2)
                  + getKthElement(nums1, nums2, n / 2 + 1)) / 2.0;
        }
    }

    /**
     * 在两个【有序数组】中查找第 k 小的元素
     * 核心思想:二分删除法(每次排除 k/2 个不可能的元素)
     */
    public int getKthElement(int[] nums1, int[] nums2, int k) {

        int length1 = nums1.length;
        int length2 = nums2.length;

        // 当前指向 nums1、nums2 的起始下标
        int index1 = 0, index2 = 0;

        while (true) {

            // 情况 1:nums1 已经被排除完
            // 那第 k 小的元素一定在 nums2 中
            if (index1 == length1) {
                return nums2[index2 + k - 1];
            }

            // 情况 2:nums2 已经被排除完
            if (index2 == length2) {
                return nums1[index1 + k - 1];
            }

            // 情况 3:k == 1
            // 只需要比较当前两个指针所指的最小值
            if (k == 1) {
                return Math.min(nums1[index1], nums2[index2]);
            }

            // 各自向前走 k/2 步(注意不能越界)
            int newIndex1 = Math.min(length1, index1 + k / 2) - 1;
            int newIndex2 = Math.min(length2, index2 + k / 2) - 1;

            // 取出这两个位置的值
            int val1 = nums1[newIndex1];
            int val2 = nums2[newIndex2];

            // 如果 nums1 的第 k/2 个元素 <= nums2 的
            // 说明 nums1 的 [index1 ... newIndex1] 这部分
            // 一定不可能是第 k 小的元素
            if (val1 <= val2) {
                // 排除 nums1 的这部分元素
                k -= (newIndex1 - index1 + 1);
                index1 = newIndex1 + 1;
            } 
            // 否则,排除 nums2 的一部分
            else {
                k -= (newIndex2 - index2 + 1);
                index2 = newIndex2 + 1;
            }
        }
    }
}
相关推荐
木风小助理2 小时前
JavaStreamAPI的性能审视,优雅语法背后的隐形成本与优化实践
java·前端·数据库
Chan162 小时前
《Java并发编程的艺术》| ConcurrentHashMap 在 JDK 1.7 与 1.8 的底层实现
java·spring boot·java-ee·intellij-idea·juc
BHXDML2 小时前
第六章:推荐算法
算法·机器学习·推荐算法
Tisfy2 小时前
LeetCode 3510.移除最小数对使数组有序 II:有序集合
算法·leetcode·题解·设计·有序集合
计算机程序猿学长3 小时前
微信小程序毕设项目推荐-基于java+springboot+mysql+微信小程序的校园外卖点餐平台基于springboot+微信小程序的校园外卖直送平台【附源码+文档,调试定制服务】
java·微信小程序·课程设计
建群新人小猿3 小时前
陀螺匠企业助手——组织框架图
android·java·大数据·开发语言·容器
CV_J3 小时前
索引库操作
java·开发语言·elasticsearch·spring cloud
汉克老师3 小时前
GESP2025年9月认证C++五级真题与解析(单选题9-15)
c++·算法·贪心算法·排序算法·归并排序·gesp5级·gesp五级
申克Lab3 小时前
STM32 FreeRTOS 消息队列
java·stm32·嵌入式硬件