【每日算法】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) 时间效率高,满足题目要求,是最优解 实现稍复杂,需处理边界条件
相关推荐
范纹杉想快点毕业16 分钟前
欧几里得算法与扩展欧几里得算法,C语言编程实现(零基础全解析)
运维·c语言·单片机·嵌入式硬件·算法
f***241117 分钟前
Bug悬案:技术侦探的破案指南
算法·bug
Swift社区19 分钟前
LeetCode 472 连接词
算法·leetcode·职场和发展
Dream it possible!20 分钟前
LeetCode 面试经典 150_二分查找_搜索旋转排序数组(114_33_C++_中等)
c++·leetcode·面试
CoovallyAIHub26 分钟前
YOLO-Maste开源:首个MoE加速加速实时检测,推理提速17.8%!
深度学习·算法·计算机视觉
清铎30 分钟前
leetcode_day13_普通数组_《绝境求生》
数据结构·算法
2301_8008951037 分钟前
hh的蓝桥杯每日一题(二分)--立定跳远
职场和发展·蓝桥杯
hetao173383740 分钟前
2026-01-09~12 hetao1733837 的刷题笔记
c++·笔记·算法
过河卒_zh15667661 小时前
情感型AI被“立规矩”,AI陪伴时代进入下半场
人工智能·算法·aigc·生成式人工智能·算法备案
wefg11 小时前
【算法】动态规划
算法·动态规划