【每日算法】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 确保 nums1partition1-1 <= nums2partition2 且 nums2partition2-1 <= nums1partition1,然后根据奇偶性计算中位数。

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) 时间效率高,满足题目要求,是最优解 实现稍复杂,需处理边界条件
相关推荐
hahaha601636 分钟前
HLS高层次综合设计技巧--数组
开发语言·嵌入式硬件·算法·fpga开发
不会就选b42 分钟前
算法日常・每日刷题--<贪心>10
算法
南京兴帝文化传媒有限公司1 小时前
本地生活服务商户GEO优化技术实践:AI大模型收录机制与地图POI权重算法拆解
大数据·人工智能·算法·生活·geo优化实操·csdn运营技巧·ai内容收录
linx2952 小时前
单元九 · 零基础路线图-第 7–9 周·类与 RAII
c语言·开发语言·数据结构·c++·嵌入式硬件·算法
布莱克6052 小时前
C++ 七大排序算法详解:选择、冒泡、插入、计数、堆、快速与归并排序
数据结构·c++·算法·排序算法
ocean21032 小时前
2025-2026年AI应用开发与Agent面试高频知识点洞察
人工智能·面试·职场和发展
shehuiyuelaiyuehao2 小时前
算法44,模拟算法,数青蛙
算法·哈希算法·散列表
笨鸟先飞的橘猫3 小时前
slg游戏面试复盘
学习·面试
hanlin033 小时前
刷题笔记:力扣第287题-寻找重复数
笔记·算法·leetcode
kevin_kang3 小时前
第02章 对话时间线与延迟分析
算法