LeetCode 4. 寻找两个正序数组的中位数 —— 二分划分的艺术

LeetCode 4. 寻找两个正序数组的中位数 ------ 二分划分的艺术

这道题是 LeetCode 前 10 题中唯一的 Hard,也是面试高频题。它的要求很直白------给两个有序数组,求中位数。朴素做法 O(m+n) 写完只需 2 分钟,但题目要求 O(log(m+n)),这就把难度直接拉满。

O(log) 意味着什么?二分查找。但二分什么?怎么二分?这才是本题的核心智慧:

  • 中位数的本质不是"找第 k 小的数",而是把两个数组划分成数量相等的左右两半,使得左半的最大值 ≤ 右半的最小值
  • 我们不需要对两个数组一起二分------只对较短的数组二分即可,另一个数组的分割位置由数学公式直接算出
  • 边界处理是本题最大的坑:分割线可能划在数组的最左端(左边没有元素)或最右端(右边没有元素),用 -∞+∞ 优雅处理

本文从暴力合并 → 双指针计数 → 二分划分层层递进,重点讲解二分划分的核心逻辑和边界处理,附 JavaScript/Python 双版本代码 + 完整推演。

前置阅读:建议先熟悉二分查找的基本模板(while left <= right 和 while left < right 两种写法),以及"第 k 小"类问题的通用思路。


问题描述

LeetCode 4. Median of Two Sorted Arrays(寻找两个正序数组的中位数)

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

要求:算法的时间复杂度为 O(log(m+n))

示例 1:

css 复制代码
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3],中位数 2

示例 2:

ini 复制代码
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4],中位数 (2 + 3) / 2 = 2.5

中位数的数学定义:

scss 复制代码
总长度 m+n 为奇数 → 中位数 = 第 ⌊(m+n)/2⌋ + 1 个元素(下标从 1 开始)
总长度 m+n 为偶数 → 中位数 = (第 (m+n)/2 个 + 第 (m+n)/2 + 1 个) / 2

图示(示例 1):

ini 复制代码
nums1: [1,     3]
         \    /
nums2:    [ 2 ]
         /      \
合并:   [1, 2, 3]  → 中位数 = 2

约束条件:

  • nums1.length == m
  • nums2.length == n
  • 0 <= m <= 1000
  • 0 <= n <= 1000
  • 1 <= m + n <= 2000
  • -10^6 <= nums1[i], nums2[i] <= 10^6

核心思想

为什么不能用简单合并?

合并两个有序数组只需要 O(m+n) 时间,这就是归并排序中 merge 函数的逻辑:

scss 复制代码
双指针逐一比较 → 合并成一个有序数组 → 取中间元素
时间 O(m+n),空间 O(m+n)

但这不符合 O(log(m+n)) 的要求。题目卡的就是这个 log------必须用二分。

中位数的本质:划分,而非查找

很多人的第一反应是"找第 k 小的数",这个思路可行(二分删减),但更优雅的理解是划分视角

ini 复制代码
中位数 = 将两个数组划分成左右两半的分割线

左半部分(共 (m+n+1)/2 个元素)      右半部分(共 (m+n)/2 个元素)
┌─────────────────────────┐    ┌─────────────────────────┐
│  nums1[0..i-1]          │    │  nums1[i..m-1]          │
│  +                      │    │  +                      │
│  nums2[0..j-1]          │    │  nums2[j..n-1]          │
└─────────────────────────┘    └─────────────────────────┘

条件:左半最大值 ≤ 右半最小值

设 i = nums1 的分割位置(分割线后第一个元素的索引)
设 j = nums2 的分割位置(分割线后第一个元素的索引)

左半元素数 = i + j = (m + n + 1) / 2(整数除法,保证左半 ≥ 右半)

核心不等式(划分合法的条件):

css 复制代码
nums1[i-1] ≤ nums2[j]   ← nums1 左半最大值 ≤ nums2 右半最小值
nums2[j-1] ≤ nums1[i]   ← nums2 左半最大值 ≤ nums1 右半最小值

如果这两个条件都满足,我们就找到了正确的划分!

如果 nums1i-1 > nums2j ,说明 nums1 左半有太大的元素,i 应该左移 (减小)。 如果 nums2j-1 > nums1i ,说明 nums2 左半有太大的元素,i 应该右移(增大 nums1 左半,间接减小 nums2 左半)。

数据结构设计

ini 复制代码
二分搜索 i 的过程:

  在 nums1 上二分 i 的位置(0 ≤ i ≤ m)
  j = (m + n + 1) / 2 - i   ← 由恒等式直接算出

  ┌──────────────────────────────────────┐
  │ nums1: [ ... i-1 │ i ... ]          │
  │ nums2: [ ... j-1 │ j ... ]          │
  │                                      │
  │ leftMax1 = nums1[i-1] (或 -∞)       │
  │ rightMin1 = nums1[i]   (或 +∞)      │
  │ leftMax2 = nums2[j-1] (或 -∞)       │
  │ rightMin2 = nums2[j]   (或 +∞)      │
  └──────────────────────────────────────┘

  边界处理:
    当 i=0 时,leftMax1 = -∞(nums1 左半为空)
    当 i=m 时,rightMin1 = +∞(nums1 右半为空)
    当 j=0 时,leftMax2 = -∞(nums2 左半为空)
    当 j=n 时,rightMin2 = +∞(nums2 右半为空)

💡 为什么只对较短的数组二分 :确保 j = (m+n+1)/2 - i 永远在 [0, n] 范围内。如果对长数组二分,j 可能溢出负数或超出 n。


思路分析

解法一:暴力合并 ⭐(新手友好,不满足要求)

核心思路:直接用归并排序的 merge 操作合并两个有序数组,然后取中位数。

less 复制代码
merge(nums1, nums2):
  双指针 i, j 从 0 开始
  每次取 nums1[i] 和 nums2[j] 中较小的放入结果数组
  合并完成后,取 merged[(m+n-1)/2] 和 merged[(m+n)/2] 的平均值

⚠️ 时间 O(m+n),空间 O(m+n),不符合题目 O(log(m+n)) 要求。面试中作为破冰思路提一下即可。

解法二:双指针计数 ⭐(不创建新数组)

核心思路:不真的合并,而是用两个指针遍历,走到"中位数位置"就取出来。

markdown 复制代码
不需要保存整个合并数组,只需要知道:
  - 第 (m+n-1)/2 个元素(中位数左侧)
  - 第 (m+n)/2 个元素   (中位数右侧)
  
遍历过程中,每取一个元素,计数 +1。计数到达目标位置时记录该元素。

⚠️ 时间 O(m+n) 依然不符合 log 要求,但空间优化到 O(1),且思路为最优解做了铺垫。

解法三:二分划分 ⭐⭐⭐(最优解,O(log min(m,n)))

核心思路 :在较短数组上二分搜索分割线位置 i,另一个数组的分割线 j 由恒等式直接算出。检查划分是否满足条件,满足即找到中位数。

css 复制代码
二分搜索 i ∈ [0, m](m 是较短数组长度):

  j = (m + n + 1) / 2 - i

  如果 nums1[i-1] > nums2[j]:   → i 太大,往左搜(right = i - 1)
  如果 nums2[j-1] > nums1[i]:   → i 太小,往右搜(left = i + 1)
  否则:                          → 找到正确划分!

找到正确划分后,中位数计算:
  - 总长奇数:中位数 = max(leftMax1, leftMax2)
  - 总长偶数:中位数 = (max(leftMax1, leftMax2) + min(rightMin1, rightMin2)) / 2

💡 关键理解j = (m+n+1)/2 - i 这个公式保证了左半部分始终有 (m+n+1)/2 个元素 。其中国际惯例用 +1 确保奇数时左半多一个,这样中位数就是左半的最大值。

三种方法对比

scss 复制代码
暴力合并(O(m+n), O(m+n)):
  nums1 ─┬─ merge ─► [完整有序数组] ─► 取中间
  nums2 ─┘
  问题:创建了完整数组,空间浪费

双指针计数(O(m+n), O(1)):
  nums1 ─┬─ 虚拟遍历 ─► 计数到中位 ─► 返回元素
  nums2 ─┘
  问题:仍然需要遍历到中间位置

二分划分(O(log min(m,n)), O(1)):
  nums1 ─┬─ 二分 i ─► 检查划分 ─► 计算中位数
  nums2 ─┘  j 由公式算出
  优势:跳过了大部分元素,直接定位分割线

代码实现

JavaScript 版本

方法一:暴力合并
javascript 复制代码
/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number}
 */
var findMedianSortedArrays = function(nums1, nums2) {
    const m = nums1.length, n = nums2.length;
    const merged = [];
    let i = 0, j = 0;

    // 归并合并
    while (i < m && j < n) {
        if (nums1[i] <= nums2[j]) {
            merged.push(nums1[i++]);
        } else {
            merged.push(nums2[j++]);
        }
    }
    while (i < m) merged.push(nums1[i++]);
    while (j < n) merged.push(nums2[j++]);

    const total = m + n;
    if (total % 2 === 1) {
        return merged[Math.floor(total / 2)];
    } else {
        return (merged[total / 2 - 1] + merged[total / 2]) / 2;
    }
};
方法二:双指针计数
javascript 复制代码
var findMedianSortedArrays = function(nums1, nums2) {
    const m = nums1.length, n = nums2.length;
    const total = m + n;
    // 需要到达的位置
    const target1 = Math.floor((total - 1) / 2); // 左中位数位置
    const target2 = Math.floor(total / 2);        // 右中位数位置

    let i = 0, j = 0, count = 0;
    let val1 = 0, val2 = 0;

    while (count <= target2) {
        let cur;
        if (i < m && (j >= n || nums1[i] <= nums2[j])) {
            cur = nums1[i++];
        } else {
            cur = nums2[j++];
        }

        if (count === target1) val1 = cur;
        if (count === target2) val2 = cur;
        count++;
    }

    return (val1 + val2) / 2;
};
方法三:二分划分 ⭐⭐⭐(最优解)
javascript 复制代码
/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number}
 */
var findMedianSortedArrays = function(nums1, nums2) {
    // 确保 nums1 是较短的数组,减少二分搜索范围
    if (nums1.length > nums2.length) {
        [nums1, nums2] = [nums2, nums1];
    }

    const m = nums1.length;
    const n = nums2.length;
    const halfLen = Math.floor((m + n + 1) / 2); // 左半部分应有的元素数

    // 在 nums1 上二分搜索分割线 i
    let left = 0, right = m;

    while (left <= right) {
        const i = Math.floor((left + right) / 2);   // nums1 的分割线
        const j = halfLen - i;                        // nums2 的分割线(由恒等式算出)

        // 四边界值,处理数组边界(用 ±Infinity 哨兵)
        const leftMax1  = i === 0 ? -Infinity : nums1[i - 1];
        const rightMin1 = i === m ?  Infinity : nums1[i];
        const leftMax2  = j === 0 ? -Infinity : nums2[j - 1];
        const rightMin2 = j === n ?  Infinity : nums2[j];

        if (leftMax1 <= rightMin2 && leftMax2 <= rightMin1) {
            // ✅ 找到正确的划分!
            if ((m + n) % 2 === 1) {
                // 总长奇数:中位数 = 左半最大值
                return Math.max(leftMax1, leftMax2);
            } else {
                // 总长偶数:中位数 = (左半最大值 + 右半最小值) / 2
                return (
                    Math.max(leftMax1, leftMax2) +
                    Math.min(rightMin1, rightMin2)
                ) / 2;
            }
        } else if (leftMax1 > rightMin2) {
            // nums1 左半有太大元素 → i 需要左移
            right = i - 1;
        } else {
            // leftMax2 > rightMin1 → nums2 左半有太大元素 → i 需要右移
            left = i + 1;
        }
    }

    // 理论上不会到达这里(题目保证输入有效)
    throw new Error('输入数组不是有序的');
};

Python 版本

方法一:暴力合并
python 复制代码
def findMedianSortedArrays(nums1: list[int], nums2: list[int]) -> float:
    """方法一:暴力合并"""
    merged = []
    i = j = 0
    m, n = len(nums1), len(nums2)

    while i < m and j < n:
        if nums1[i] <= nums2[j]:
            merged.append(nums1[i])
            i += 1
        else:
            merged.append(nums2[j])
            j += 1

    while i < m:
        merged.append(nums1[i])
        i += 1
    while j < n:
        merged.append(nums2[j])
        j += 1

    total = m + n
    if total % 2 == 1:
        return merged[total // 2]
    else:
        return (merged[total // 2 - 1] + merged[total // 2]) / 2
方法二:双指针计数
python 复制代码
def findMedianSortedArrays(nums1: list[int], nums2: list[int]) -> float:
    """方法二:双指针计数"""
    m, n = len(nums1), len(nums2)
    total = m + n
    target1 = (total - 1) // 2
    target2 = total // 2

    i = j = count = 0
    val1 = val2 = 0

    while count <= target2:
        if i < m and (j >= n or nums1[i] <= nums2[j]):
            cur = nums1[i]
            i += 1
        else:
            cur = nums2[j]
            j += 1

        if count == target1:
            val1 = cur
        if count == target2:
            val2 = cur
        count += 1

    return (val1 + val2) / 2
方法三:二分划分 ⭐⭐⭐(最优解)
python 复制代码
def findMedianSortedArrays(nums1: list[int], nums2: list[int]) -> float:
    """方法三:二分划分,O(log min(m,n))"""
    # 确保 nums1 是较短的数组
    if len(nums1) > len(nums2):
        nums1, nums2 = nums2, nums1

    m, n = len(nums1), len(nums2)
    half_len = (m + n + 1) // 2  # 左半部分应有的元素数

    left, right = 0, m

    while left <= right:
        i = (left + right) // 2   # nums1 的分割线
        j = half_len - i           # nums2 的分割线

        # 四边界值,用 float('inf') 和 float('-inf') 做哨兵
        left_max1  = nums1[i - 1] if i > 0 else float('-inf')
        right_min1 = nums1[i]     if i < m else float('inf')
        left_max2  = nums2[j - 1] if j > 0 else float('-inf')
        right_min2 = nums2[j]     if j < n else float('inf')

        if left_max1 <= right_min2 and left_max2 <= right_min1:
            # 找到正确的划分
            if (m + n) % 2 == 1:
                return max(left_max1, left_max2)
            else:
                return (
                    max(left_max1, left_max2) +
                    min(right_min1, right_min2)
                ) / 2
        elif left_max1 > right_min2:
            # nums1 左半有太大元素 → i 左移
            right = i - 1
        else:
            # nums2 左半有太大元素 → i 右移(间接减小 nums2 左半)
            left = i + 1

    raise ValueError("输入数组不是有序的")

逐步推演

二分划分法完整推演(示例 1)

nums1 = [1, 3], nums2 = [2] 为例,逐步展示二分搜索过程:

ini 复制代码
nums1 = [1, 3]  长度 m = 2
nums2 = [2]     长度 n = 1
总长 = 3 (奇数)

halfLen = (3 + 1) / 2 = 2  ← 左半需要 2 个元素


═══════════════ 初始化 ═══════════════

left = 0, right = 2 (m)


═══════════════ 第 1 轮二分 ═══════════════

i = (0 + 2) / 2 = 1
j = halfLen - i = 2 - 1 = 1

划分状态:
  nums1: [ 1 │ 3 ]
           ↑ i=1
  nums2: [ 2 │   ]
           ↑ j=1

计算边界值:
  leftMax1  = nums1[0] = 1        (i=1, i>0 ✓)
  rightMin1 = nums1[1] = 3        (i=1, i<2 ✓)
  leftMax2  = nums2[0] = 2        (j=1, j>0 ✓)
  rightMin2 = +∞                  (j=1, j==n, 哨兵)

检查条件:
  leftMax1 (1) ≤ rightMin2 (+∞)  ✅
  leftMax2 (2) ≤ rightMin1 (3)   ✅

  🎯 找到正确划分!

总长 3 是奇数 → 中位数 = max(leftMax1, leftMax2) = max(1, 2) = 2
返回 2.0 ✅

二分划分法完整推演(示例 2)

nums1 = [1, 2], nums2 = [3, 4] 为例:

ini 复制代码
nums1 = [1, 2]  长度 m = 2
nums2 = [3, 4]  长度 n = 2
总长 = 4 (偶数)

halfLen = (4 + 1) / 2 = 2  ← 左半需要 2 个元素


═══════════════ 初始化 ═══════════════

left = 0, right = 2


═══════════════ 第 1 轮二分 ═══════════════

i = (0 + 2) / 2 = 1
j = 2 - 1 = 1

划分状态:
  nums1: [ 1 │ 2 ]
           ↑ i=1
  nums2: [ 3 │ 4 ]
           ↑ j=1

计算边界值:
  leftMax1  = nums1[0] = 1
  rightMin1 = nums1[1] = 2
  leftMax2  = nums2[0] = 3
  rightMin2 = nums2[1] = 4

检查条件:
  leftMax1 (1) ≤ rightMin2 (4)  ✅
  leftMax2 (3) ≤ rightMin1 (2)  ❌  3 > 2!

  nums2 左半有太大的元素 → i 需要右移
  left = i + 1 = 2


═══════════════ 第 2 轮二分 ═══════════════

i = (2 + 2) / 2 = 2
j = 2 - 2 = 0

划分状态:
  nums1: [ 1, 2 │   ]
              ↑ i=2
  nums2: [ │ 3, 4 ]
         ↑ j=0

计算边界值:
  leftMax1  = nums1[1] = 2    (i=2, i>0 ✓)
  rightMin1 = +∞              (i=2, i==m, 哨兵)
  leftMax2  = -∞              (j=0, j==0, 哨兵)
  rightMin2 = nums2[0] = 3    (j=0, j<n ✓)

检查条件:
  leftMax1 (2) ≤ rightMin2 (3)  ✅
  leftMax2 (-∞) ≤ rightMin1 (+∞) ✅

  🎯 找到正确划分!

总长 4 是偶数 → 中位数 = (max(左) + min(右)) / 2
  = (max(2, -∞) + min(+∞, 3)) / 2
  = (2 + 3) / 2
  = 2.5 ✅

边界情况推演:一个数组为空

nums1 = [], nums2 = [1] 为例:

ini 复制代码
nums1 = []  长度 m = 0
nums2 = [1] 长度 n = 1
总长 = 1 (奇数)

halfLen = (1 + 1) / 2 = 1

left = 0, right = 0
i = 0, j = 1 - 0 = 1

划分状态:
  nums1: [ │ ]     ← 空数组,分割线在最左端
         ↑ i=0
  nums2: [ 1 │ ]
           ↑ j=1

计算边界值:
  leftMax1  = -∞              (i=0, 哨兵)
  rightMin1 = +∞              (i=0, i==m=0, 哨兵)
  leftMax2  = nums2[0] = 1    (j=1, j>0 ✓)
  rightMin2 = +∞              (j=1, j==n, 哨兵)

检查条件:
  leftMax1 (-∞) ≤ rightMin2 (+∞)  ✅
  leftMax2 (1) ≤ rightMin1 (+∞)   ✅

  🎯 找到正确划分!

总长 1 是奇数 → 中位数 = max(-∞, 1) = 1
返回 1.0 ✅

💡 哨兵 -∞+∞ 的妙用 :它们让边界情况的比较逻辑和正常情况完全统一------空半边的"最大值"是 -∞(永远不会大于对面),"最小值"是 +∞(永远不会小于对面)。不需要写任何 if-else 特判。


复杂度分析

方法 时间复杂度 空间复杂度 优点 缺点
暴力合并 O(m+n) O(m+n) 思路最直观,一行 merge 搞定 空间和时间都不满足要求
双指针计数 O(m+n) O(1) 不创建额外数组 仍需遍历到中位位置,O(n) 不满足要求
二分划分 ⭐⭐⭐ O(log min(m,n)) O(1) 最优解,完全符合要求 边界处理需要细心,理解门槛较高
  • 时间复杂度:二分划分法每次迭代排除一半的搜索空间(在较短数组上),所以是 O(log min(m,n))。这是本题能达到的理论下限
  • 空间复杂度:二分划分只用了常数个变量(left, right, i, j, 四个边界值),O(1)
  • 稳定性:中位数计算本身不涉及元素顺序改变,三种方法结果完全一致

二分搜索过程分解

css 复制代码
以 m=4, n=6 为例:

二分搜索空间大小: m+1 = 5 个可能的 i 值 {0, 1, 2, 3, 4}
最多需要 log₂(5) ≈ 3 次迭代

每次迭代的工作量:
  计算 j       : O(1)
  取四个边界值  : O(1)(数组随机访问)
  判断条件      : O(1)
  ─────────────────────
  每轮耗时 O(1),共 O(log min(m,n)) 轮

与 O(m+n) 的对比(m=n=1000):
  O(m+n) ≈ 2000 步
  O(log min(m,n)) ≈ log₂(1000) ≈ 10 步
  快了 200 倍!

举一反三

本题在"有序数组查找"体系中的位置

arduino 复制代码
有序数组中的二分查找体系:

LeetCode 4. 寻找两个正序数组的中位数(Hard)
  ├── 基础:二分查找 → LeetCode 704
  ├── 变体:搜索旋转排序数组 → LeetCode 33, 81
  ├── 扩展:两个有序数组的第 K 小 → 本题的"第K小"视角
  │   └── 本质:每次排除 k/2 个元素(二分删减法)
  └── 应用:区间检索、数据库索引合并、流式中位数

关联题目

题目 核心思想 与本题的关系
704. 二分查找 经典二分模板 二分搜索的基础
33. 搜索旋转排序数组 二分 + 有序半段判定 二分的变体------不是全局有序但可以二分
240. 搜索二维矩阵 II 从右上角开始,每次排除一行或一列 每次排除一部分的二分思想
295. 数据流的中位数 双堆(最大堆+最小堆)维护中位数 中位数的动态版本------数据流场景
658. 找到 K 个最接近的元素 二分 + 滑动窗口 二分定位 + 窗口扩展
1095. 山脉数组中查找目标值 三分查找峰顶 + 二分查找 先找峰,再分两段二分

"第 K 小"通用视角(题外拓展)

本题也可以从"求两个有序数组的第 K 小元素"的角度解决:

ini 复制代码
思路:每次比较 nums1[k/2-1] 和 nums2[k/2-1]
  - 较小的那个数组的前 k/2 个元素不可能是第 k 小的
  - 排除它们,k 减去排除的数量,继续递归

递推公式:k = (m+n+1)/2(奇数)或 k = (m+n)/2, (m+n)/2+1(偶数)

findKth(nums1, nums2, k):
  1. 如果 nums1 为空 → 返回 nums2[k-1]
  2. 如果 k == 1 → 返回 min(nums1[0], nums2[0])
  3. 比较 nums1[k/2-1] 和 nums2[k/2-1]
     → 删除较小数组的前 k/2 个元素
     → 递归 findKth(剩余, nums2, k - 删除数量)

但二分划分法更加优雅------把"找第 k 小"转化为"划分"问题,边界处理用哨兵统一。这两种视角是等价的,理解其一即可。

延伸思考

在实际面试中,面试官可能追问:

  1. "为什么是 O(log min(m,n)) 而不是 O(log(m+n))?" → 因为我们只在较短数组上二分搜索,搜索空间是 min(m,n)+1 个可能位置。对短数组二分就已经能确定整个划分了,不需要对两个数组都二分

  2. "halfLen 公式里的 +1 是什么意思?"(m+n+1)/2 中的 +1 让奇数总长时左半多分一个元素。这样中位数就是 max(左半最大值),不用再去右半找。这是一个让代码统一处理奇偶的技巧

  3. "为什么 i 的范围是 0, m 而不是 0, m-1?" → i 是分割线位置(分割线后第一个元素的索引),i=0 表示 nums1 全部在右半,i=m 表示全部在左半。所以一共 m+1 个可能位置

  4. "哨兵 ±∞ 不会在比较时出错吗?" → 不会。-∞ 和任何真实数比较都是更小,+∞ 和任何真实数比较都是更大,拿它们做 max/min 的结果就是真实数。这正是我们想要的行为------空半边不影响结果

  5. "如果两个数组都很大(百万级别),这个算法还能用吗?" → 可以。二分划分只需要 O(log min(m,n)) 次迭代,百万级别的 log 也只有约 20 次。但需要注意整数溢出------halfLen = (m+n+1)/2 在极端情况下可能溢出,实际中用 m + Math.floor((n - m + 1) / 2) 更安全


总结

题目 难度 核心思想 推荐解法
4. 寻找两个正序数组的中位数 🔴 Hard 二分划分、哨兵边界、中位数本质 二分划分法

这道题的核心智慧有三层:

层次 理解 关键点
第一层 中位数不是"找第 k 小",而是"划分" 划分左右两半,左 ≤ 右
第二层 只对短数组二分 j = halfLen - i 自动算出另一个分割线
第三层 哨兵 ±∞ 统一边界 不需要为边界情况写特判

一句话口诀短数组上二分 i,j 由公式自动算,四边界值做哨兵,交叉比较判合法。左半最大是奇数答,最大最小平均是偶数答。

记住一件事:二分查找不只是在"一个有序数组"里找目标值。本题告诉你------二分同样可以在两个有序数组上找"分割线",而这种"条件判定 + 二分搜索空间"的模式是二分查找最强大的推广。掌握它,一大类 Hard 问题都能迎刃而解。


关于作者:LeetCode 刷题中,致力于用最清晰的方式讲透算法题。欢迎在评论区交流讨论!
相关题解33. 搜索旋转排序数组(二分变体) | 295. 数据流的中位数(动态中位数) | 240. 搜索二维矩阵 II(二分排除思想) | 二分专题持续更新中,关注不迷路。

相关推荐
默_笙1 小时前
😢 我被 JS 的 this 整不会了:同一个函数,换个“地区”调用,结果就不一样
javascript
Tom@敲代码2 小时前
js学习笔记-01
javascript·笔记·学习
z小猫不吃鱼2 小时前
模型剪枝经典论文精读:Channel Pruning for Accelerating Very Deep Neural Networks
算法·机器学习·剪枝
木西2 小时前
告别机械重复:使用 Node.js + Playwright 构建 24 小时全自动测试网领水脚本
前端·javascript·node.js
lewis_lk2 小时前
uv: Python 新一代极速包管理工具
python
不好听6132 小时前
JavaScript Promise 从入门到精通(一):为什么需要 Promise 以及它是什么
javascript
njsgcs2 小时前
已知链中心距用节数算中间电机摆放位置 常量弧长 三链轮弦长模型算法
算法
qdkfz2 小时前
JavaScript 如何解析 Outlook .msg 文件 —— @kenjiuno/msgreader
前端·javascript
promising_xxx2 小时前
深度学习个人开源知识库 深度筑基 | DeepBase
人工智能·python·深度学习·计算机视觉·ai·语言模型·nlp