Leecode#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(长度 m)和 nums2(长度 n),合并后总长度 total = m + n。中位数将合并数组分成两半:

  • 左半部分 包含 half = (total + 1) / 2 个元素(奇数时多一个,中位数就是左半部分的最大值)
  • 右半部分包含剩余元素

我们不去真正合并数组,而是对较短的数组做二分,找到一个分割位置 i(nums1 的分割点),从而推出 j = half - i(nums2 的分割点),使得:

复制代码
左半部分所有元素 ≤ 右半部分所有元素

nums1:  [ ... nums1Left ]  | [ nums1Right ... ]
nums2:  [ ... nums2Left ]  | [ nums2Right ... ]
              左半部分           右半部分

满足条件:max(nums1Left, nums2Left) ≤ min(nums1Right, nums2Right)


Java语言

java 复制代码
class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        if(nums1.length>nums2.length){
          int[] temp=nums1;
          nums1=nums2;
          nums2=temp;
        }
        int m=nums1.length,n=nums2.length;
        int total=m+n;
        int half=(total+1)/2;
        
        int left=0,right=m;
        while(left<=right){
            int i=(left+right)/2;
            int j=half-i;
            int nums1Left=i>0?nums1[i-1]:Integer.MIN_VALUE;
            int nums1Right=i<m?nums1[i]:Integer.MAX_VALUE;
            int nums2Left=j>0?nums2[j-1]:Integer.MIN_VALUE;
            int nums2Right=j<n?nums2[j]:Integer.MAX_VALUE;
            if(nums1Left <= nums2Right && nums2Left <= nums1Right){
                if(total%2==1){
                    return(double)Math.max(nums1Left,nums2Left);
                }
                else {
                    return (Math.max(nums1Left, nums2Left) + Math.min(nums1Right, nums2Right)) / 2.0;
                }
            } else if (nums1Left > nums2Right) {
                right = i - 1;
            } else {
                left = i + 1;
            }
        }
 
        return 0.0;
    }
}

逐行解释

1. 保证 nums1 是较短的数组

java 复制代码
if(nums1.length > nums2.length){

int[] temp = nums1;

nums1 = nums2;

nums2 = temp;

}

为什么要交换? 我们只对 nums1 做二分搜索,搜索范围是 [0, m]。如果 nums1 更短,二分次数就是 O(log(min(m,n)))。同时,j = half - i 必须满足 0 ≤ j ≤ n,如果 nums1 太长,i 可能很大导致 j 变成负数,就越界了。

2. 初始化变量

java 复制代码
int m = nums1.length, n = nums2.length;

int total = m + n;

int half = (total + 1) / 2;
  • total:合并后的总元素个数
  • half:左半部分应包含的元素个数。用 (total + 1) / 2 而非 total / 2,是为了奇数时左半部分多一个元素,中位数直接取左半最大值即可,逻辑更统一
total half 说明
7(奇) 4 左半 4 个,右半 3 个,中位数 = 左半最大值
8(偶) 4 左半 4 个,右半 4 个,中位数 = (左半最大 + 右半最小) / 2

3. 二分搜索

java 复制代码
int left = 0, right = m;

在 nums1 的分割位置上做二分。i 的范围是 [0, m]

  • i = 0:nums1 的元素全在右半部分
  • i = m:nums1 的元素全在左半部分
java 复制代码
while(left <= right){

标准二分循环,left 超过 right 时说明搜索空间耗尽(正常情况下一定会提前 return,不会走到这里)。

4. 计算分割点

java 复制代码
int i = (left + right) / 2;

int j = half - i;
  • i:nums1 中有 i 个元素划入左半部分

  • j:nums2 中有 j 个元素划入左半部分

  • 两者之和必须等于 half,所以 j = half - i

    nums1: [0, 1, ..., i-1] | [i, i+1, ..., m-1]
    nums2: [0, 1, ..., j-1] | [j, j+1, ..., n-1]
    ←--- left ---→ ←--- right ---→

5. 取出分割线两侧的四个关键元素

java 复制代码
int nums1Left = i > 0 ? nums1[i-1] : Integer.MIN_VALUE;

int nums1Right = i < m ? nums1[i] : Integer.MAX_VALUE;

int nums2Left = j > 0 ? nums2[j-1] : Integer.MIN_VALUE;

int nums2Right = j < n ? nums2[j] : Integer.MAX_VALUE;
变量 含义 边界处理
nums1Left nums1 左半部分的最后一个元素 i=0 时左半没有元素,设为 MIN_VALUE(保证不干扰比较)
nums1Right nums1 右半部分的第一个元素 i=m 时右半没有元素,设为 MAX_VALUE
nums2Left nums2 左半部分的最后一个元素 j=0 时设为 MIN_VALUE
nums2Right nums2 右半部分的第一个元素 j=n 时设为 MAX_VALUE

MIN_VALUE / MAX_VALUE 的原因:当某个数组全部被分到一侧时,对应的"边界元素"不存在,用极值确保后续比较逻辑不出错。

6. 判断是否找到正确的分割位置

java 复制代码
if(nums1Left <= nums2Right && nums2Left <= nums1Right){

这是核心判断条件。正确的分割要求:

复制代码
nums1 的左半最大 ≤ nums2 的右半最小
nums2 的左半最大 ≤ nums1 的右半最小

等价于:左半部分所有元素 ≤ 右半部分所有元素。

找到了,计算中位数:
java 复制代码
if(total % 2 == 1){

return (double) Math.max(nums1Left, nums2Left);

}

总长度为奇数:中位数 = 左半部分的最大值(左半比右半多一个元素)。

java 复制代码
else {

return (Math.max(nums1Left, nums2Left) + Math.min(nums1Right, nums2Right)) / 2.0;

}

总长度为偶数 :中位数 = (左半最大值 + 右半最小值) / 2。/ 2.0 确保浮点除法。

没找到,调整二分边界:
java 复制代码
} else if (nums1Left > nums2Right) {

right = i - 1;

nums1Left > nums2Right:nums1 划进左半的元素太大了,说明 i 偏大,需要左移 分割点,缩小 right

java 复制代码
} else {

left = i + 1;

}

否则是 nums2Left > nums1Right:nums2 划进左半的元素太大了,说明 j 偏大即 i 偏小,需要右移 分割点,增大 left

7. 兜底返回

java 复制代码
return 0.0;

理论上不会执行到这一行。只要输入合法,循环内一定会 return。这是 Java 编译要求的语法兜底。


图解示例

复制代码
nums1 = [1, 3]    m = 2
nums2 = [2, 4, 5] n = 3
total = 5(奇数), half = 3

二分过程:

轮次 left right i j nums1Left nums1Right nums2Left nums2Right 判断
1 0 2 1 2 1 3 4 5 nums2Left(4) > nums1Right(3) → left=2
2 2 2 2 1 3 MAX 2 4 3≤4 且 2≤MAX → 找到

奇数,返回 max(3, 2) = 3.0,即中位数 3。合并后 [1,2,3,4,5],中位数确实是 3。

相关推荐
qq_267612891 小时前
项目集、里程碑与迭代联动:Gitee项目管理如何支撑多团队规模化交付与进度风险治理
前端·gitee
土司大王1 小时前
LeetCode hot100——除了自身以外数组的乘积
数据结构·算法·leetcode
IvanCodes1 小时前
RAG 实战教程(三):向量数据库检索算法,KNN、IVF、HNSW 与 Faiss 实战
人工智能·算法·agent
赵广陆1 小时前
企业实战:Web服务端搭建
前端·langchain·langgraph
常宇佳1 小时前
vue3 实战系列之无法识别vue组件
前端·javascript·vue.js
阿无,1 小时前
布隆过滤器
java·算法·哈希算法
掘金者阿豪1 小时前
Codex 怎么突然变慢了?一个需求跑几十分钟,我才发现它的工作方式已经变了
前端·后端
Pocker_Spades_A1 小时前
飞算JavaAI 48分钟能做完Java全栈项目吗?
java·springboot·#ai编程·#飞算javaai·#java代码生成·#aicoding模型·#java开发
用户931456355661 小时前
从 3 秒到 300 毫秒:一次真实的前端首屏性能优化全记录
前端