【每日算法】LeetCode 4. 寻找两个正序数组的中位数

对前端开发者而言,学习算法绝非为了"炫技"。它是你从"页面构建者"迈向"复杂系统设计者"的关键阶梯。它将你的编码能力从"实现功能"提升到"设计优雅、高效解决方案"的层面。从现在开始,每天投入一小段时间,结合前端场景去理解和练习,你将会感受到自身技术视野和问题解决能力的质的飞跃。------ 算法:资深前端开发者的进阶引擎

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

1. 题目描述

给定两个大小分别为 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
  • -10^6 <= nums1[i], nums2[i] <= 10^6

2. 问题分析

中位数是一组数据中间位置的数:如果数据个数为奇数,中位数是排序后的中间元素;如果为偶数,则是中间两个数的平均值。对于两个正序数组,问题转化为找到合并后数组的中位数,但无需实际合并以优化性能。

核心挑战在于满足 O(log(m+n)) 时间复杂度。直接合并或遍历需要线性时间,不满足要求。因此,必须利用数组有序的特性,通过二分查找或类似分治策略来快速定位中位数位置。关键洞察是:中位数将数据集分成两部分,左边部分的所有元素小于等于右边部分。对于两个数组,我们可以寻找一个划分点,使得两个数组的左边元素个数之和为总元素的一半,且左边最大值小于等于右边最小值

3. 解题思路

3.1 思路一:合并后查找(暴力法)

将两个数组合并为一个有序数组,然后直接取中位数。这种方法简单直观,但时间复杂度和空间复杂度均为 O(m+n),不满足题目要求,仅作为基础理解。

3.2 思路二:双指针遍历法

使用两个指针模拟合并过程,遍历到中位数位置而不实际合并数组。时间复杂度为 O(m+n),空间复杂度为 O(1)。虽然空间优化,但时间仍为线性,不满足要求。

3.3 思路三:二分查找法(最优解)

在较短数组上执行二分查找,寻找划分点使得两个数组的左边部分合并后恰好为总元素的一半,且满足左边最大值小于等于右边最小值。时间复杂度为 O(log(min(m,n))),空间复杂度为 O(1),满足题目要求,是最优解。原理是:设总长度 total = m+n,左边部分大小 leftSize = (total+1)//2(向上取整)。在 nums1(较短数组)中设划分点 partition1,则 nums2 的划分点 partition2 = leftSize - partition1。通过调整 partition1 确保 nums1[partition1-1] <= nums2[partition2] 且 nums2[partition2-1] <= nums1[partition1],然后根据奇偶性计算中位数。

4. 各思路代码实现

4.1 思路一代码:合并后查找

javascript 复制代码
var findMedianSortedArrays = function(nums1, nums2) {
    const merged = [];
    let i = 0, j = 0;
    while (i < nums1.length && j < nums2.length) {
        if (nums1[i] < nums2[j]) {
            merged.push(nums1[i++]);
        } else {
            merged.push(nums2[j++]);
        }
    }
    while (i < nums1.length) merged.push(nums1[i++]);
    while (j < nums2.length) merged.push(nums2[j++]);
    
    const len = merged.length;
    if (len % 2 === 0) {
        return (merged[len/2 - 1] + merged[len/2]) / 2;
    } else {
        return merged[Math.floor(len/2)];
    }
};

4.2 思路二代码:双指针遍历法

javascript 复制代码
var findMedianSortedArrays = function(nums1, nums2) {
    const m = nums1.length, n = nums2.length;
    const total = m + n;
    const mid = Math.floor(total / 2);
    let i = 0, j = 0;
    let prev = 0, curr = 0;
    
    for (let k = 0; k <= mid; k++) {
        prev = curr;
        if (i < m && (j >= n || nums1[i] < nums2[j])) {
            curr = nums1[i++];
        } else {
            curr = nums2[j++];
        }
    }
    
    if (total % 2 === 0) {
        return (prev + curr) / 2;
    } else {
        return curr;
    }
};

4.3 思路三代码:二分查找法(最优解)

javascript 复制代码
var findMedianSortedArrays = function(nums1, nums2) {
    // 确保 nums1 为较短数组,以优化二分查找
    if (nums1.length > nums2.length) {
        [nums1, nums2] = [nums2, nums1];
    }
    
    const m = nums1.length, n = nums2.length;
    const total = m + n;
    const leftSize = Math.floor((total + 1) / 2); // 左边部分大小
    
    let left = 0, right = m;
    while (left <= right) {
        const partition1 = Math.floor((left + right) / 2);
        const partition2 = leftSize - partition1;
        
        // 处理边界情况
        const maxLeft1 = partition1 === 0 ? -Infinity : nums1[partition1 - 1];
        const minRight1 = partition1 === m ? Infinity : nums1[partition1];
        const maxLeft2 = partition2 === 0 ? -Infinity : nums2[partition2 - 1];
        const minRight2 = partition2 === n ? Infinity : nums2[partition2];
        
        if (maxLeft1 <= minRight2 && maxLeft2 <= minRight1) {
            // 划分有效,计算中位数
            if (total % 2 === 0) {
                return (Math.max(maxLeft1, maxLeft2) + Math.min(minRight1, minRight2)) / 2;
            } else {
                return Math.max(maxLeft1, maxLeft2);
            }
        } else if (maxLeft1 > minRight2) {
            // 划分太靠右,向左调整
            right = partition1 - 1;
        } else {
            // 划分太靠左,向右调整
            left = partition1 + 1;
        }
    }
    
    throw new Error("Input arrays are not sorted.");
};

5. 各实现思路的复杂度、优缺点对比表格

思路 时间复杂度 空间复杂度 优点 缺点
合并后查找 O(m+n) O(m+n) 简单直观,易于实现 时间和空间效率低,不满足题目要求
双指针遍历 O(m+n) O(1) 空间效率高,无需额外数组 时间效率仍为线性,不满足 O(log(m+n))
二分查找法 O(log(min(m,n))) O(1) 时间效率高,满足题目要求,是最优解 实现稍复杂,需处理边界条件
相关推荐
长安er2 小时前
LeetCode 62/64/5/1143多维动态规划核心题型总结
算法·leetcode·mybatis·动态规划
鹿角片ljp2 小时前
力扣 83: 删除排序链表中的重复元素(Java实现)
java·leetcode·链表
LYFlied2 小时前
【每日算法】LeetCode 208. 实现 Trie (前缀树)
数据结构·算法·leetcode·面试·职场和发展
代码游侠2 小时前
应用——MPlayer 媒体播放器系统代码详解
linux·运维·笔记·学习·算法
学编程就要猛2 小时前
算法:3.快乐数
java·算法
GSDjisidi2 小时前
国内IT软考证报考流程及前期准备,一篇解读
面试·职场和发展
AI科技星2 小时前
统一场论框架下万有引力常数的量子几何涌现与光速关联
数据结构·人工智能·算法·机器学习·重构
仰泳的熊猫2 小时前
1109 Group Photo
数据结构·c++·算法·pat考试
未来之窗软件服务2 小时前
幽冥大陆(五十八)php1024位密码生成—东方仙盟筑基期
开发语言·算法·仙盟创梦ide·东方仙盟