跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava算法

两个有序数组的中位数:五种解法与性能对比

对于在两个有序数组中找中位数的问题,转换为寻找第 k 小元素,利用二分切割或递归剔除一半数据来达到 O(log(m+n)) 要求。给出了五种 Java 实现:标准的二分分割法、递归版和迭代版寻找第 k 小、朴素的合并归并以及另一种基于中位数性质的二分,并对比了时间、空间和适用场景。核心建议是面试优先用二分分割法,工程中偶有第 k 小通用查询需求时可选用迭代版。

Elasticer发布于 2026/6/300 浏览
两个有序数组的中位数:五种解法与性能对比

LeetCode 第 4 题要求在两个正序数组中找出中位数,时间复杂度 O(log(m+n))。这题主要考察对中位数本质和二分查找的理解。下面先梳理核心思路,再给出五种实现,最后分析不同场景怎么选。

问题

输入两个升序数组 nums1 和 nums2(大小分别为 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

约束:0 <= m,n <= 1000,1 <= m+n <= 2000,元素范围 [-10⁶, 10⁶]。

核心思路

中位数可以转化为第 k 小问题。设总长度 L = m+n,则:

  • 奇数时,中位数是第 (L+1)/2 小的数。
  • 偶数时,中位数是第 L/2 小和第 L/2+1 小的平均值。

于是只要能在 O(log(m+n)) 内找到第 k 小元素就能求解。数组有序→二分,每次排除一部分不可能的元素。主要有两类写法:基于分割点的二分(标准做法)和递归/迭代地'砍掉' k/2 个元素。

另外注意,为了二分范围尽可能小,一般让 nums1 作为较短数组。

解法一:二分分割(标准)

思路:将两个数组分成左右两部分,使得左半部分共 (L+1)/2 个元素,且左边最大值 ≤ 右边最小值。分割点 i 在 nums1 上二分,j 由 j = leftCount - i 确定。检查条件 nums1[i-1] <= nums2[j] 且 nums2[j-1] <= nums1[i]。满足条件后根据总长度奇偶取最大值或平均值。

实现如下:

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        if (nums1.length > nums2.length) {
            return findMedianSortedArrays(nums2, nums1);
        }
        int m = nums1.length;
        int n = nums2.length;
        int total = m + n;
        int leftCount = (total + 1) / 2;
        int left = 0, right = m;
        while (left <= right) {
            int i = left + (right - left) / 2;
            int j = leftCount - i;
            int nums1LeftMax = (i == 0) ? Integer.MIN_VALUE : nums1[i - 1];
            int nums1RightMin = (i == m) ? Integer.MAX_VALUE : nums1[i];
            int nums2LeftMax = (j == 0) ? Integer.MIN_VALUE : nums2[j - 1];
            int nums2RightMin = (j == n) ? Integer.MAX_VALUE : nums2[j];
            if (nums1LeftMax <= nums2RightMin && nums2LeftMax <= nums1RightMin) {
                if (total % 2 == 1) {
                    return Math.max(nums1LeftMax, nums2LeftMax);
                } else {
                    int leftMax = Math.max(nums1LeftMax, nums2LeftMax);
                    int rightMin = Math.min(nums1RightMin, nums2RightMin);
                    return (leftMax + rightMin) / 2.0;
                }
            } else if (nums1LeftMax > nums2RightMin) {
                right = i - 1;
            } else {
                left = i + 1;
            }
        }
        return 0.0;
    }
}

时间复杂度 O(log(min(m,n))),空间 O(1)。这是面试中最常考察的写法,边界处理需要细心。

解法二:递归寻找第 k 小

把问题还原成找第 k 小的元素,每次在两个数组各取第 k/2 个元素比较,较小者所属数组的前半段不可能包含第 k 小,直接削掉,递归缩小 k。

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length, n = nums2.length, total = m + n;
        if (total % 2 == 1) {
            return findKth(nums1, 0, m - 1, nums2, 0, n - 1, (total + 1) / 2);
        } else {
            double left = findKth(nums1, 0, m - 1, nums2, 0, n - 1, total / 2);
            double right = findKth(nums1, 0, m - 1, nums2, 0, n - 1, total / 2 + 1);
            return (left + right) / 2.0;
        }
    }
    private double findKth(int[] nums1, int start1, int end1, int[] nums2, int start2, int end2, int k) {
        int len1 = end1 - start1 + 1;
        int len2 = end2 - start2 + 1;
        if (len1 > len2) return findKth(nums2, start2, end2, nums1, start1, end1, k);
        if (len1 == 0) return nums2[start2 + k - 1];
        if (k == 1) return Math.min(nums1[start1], nums2[start2]);
        int i = start1 + Math.min(len1, k / 2) - 1;
        int j = start2 + Math.min(len2, k / 2) - 1;
        if (nums1[i] <= nums2[j]) {
            return findKth(nums1, i + 1, end1, nums2, start2, end2, k - (i - start1 + 1));
        } else {
            return findKth(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1));
        }
    }
}

复杂度 O(log(m+n)),递归栈深度 O(log(m+n))。思路比较直观,但递归有额外开销,数据规模不大时不用担心。

解法三:合并数组(不满足要求)

直接用双指针归并两个数组,然后取中位数。时间复杂度 O(m+n),空间 O(m+n),不符合原题的对数要求,但实现最简单,适合小数据量或快速验证。

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length, n = nums2.length;
        int[] merged = new int[m + n];
        int i = 0, j = 0, k = 0;
        while (i < m && j < n) {
            if (nums1[i] <= nums2[j]) merged[k++] = nums1[i++];
            else merged[k++] = nums2[j++];
        }
        while (i < m) merged[k++] = nums1[i++];
        while (j < n) merged[k++] = nums2[j++];
        int total = m + n;
        if (total % 2 == 1) return merged[total / 2];
        else return (merged[total / 2 - 1] + merged[total / 2]) / 2.0;
    }
}

解法四:迭代寻找第 k 小

把解法二的递归改成循环,避免递归栈开销。每次更新 k 和起始指针,直到 k=1 或某一数组用尽。

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length, n = nums2.length, total = m + n;
        if (total % 2 == 1) return findKthIterative(nums1, nums2, (total + 1) / 2);
        else {
            double left = findKthIterative(nums1, nums2, total / 2);
            double right = findKthIterative(nums1, nums2, total / 2 + 1);
            return (left + right) / 2.0;
        }
    }
    private double findKthIterative(int[] nums1, int[] nums2, int k) {
        int m = nums1.length, n = nums2.length;
        int index1 = 0, index2 = 0;
        while (true) {
            if (index1 == m) return nums2[index2 + k - 1];
            if (index2 == n) return nums1[index1 + k - 1];
            if (k == 1) return Math.min(nums1[index1], nums2[index2]);
            int half = k / 2;
            int newIndex1 = Math.min(index1 + half, m) - 1;
            int newIndex2 = Math.min(index2 + half, n) - 1;
            if (nums1[newIndex1] <= nums2[newIndex2]) {
                k -= (newIndex1 - index1 + 1);
                index1 = newIndex1 + 1;
            } else {
                k -= (newIndex2 - index2 + 1);
                index2 = newIndex2 + 1;
            }
        }
    }
}

复杂度同解法二,但空间 O(1)。竞赛中为了快零点几毫秒可能会用这种写法。

解法五:中位数性质的二分

实际上和解法一原理相同,只是写法略有不同:直接在 nums1 上二分 i,利用 j = halfLen - i 检查分割条件,判断 i 偏大还是偏小。

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length, n = nums2.length;
        if (m > n) return findMedianSortedArrays(nums2, nums1);
        int left = 0, right = m;
        int halfLen = (m + n + 1) / 2;
        while (left <= right) {
            int i = (left + right) / 2;
            int j = halfLen - i;
            if (i < right && nums2[j - 1] > nums1[i]) {
                left = i + 1;
            } else if (i > left && nums1[i - 1] > nums2[j]) {
                right = i - 1;
            } else {
                int maxLeft;
                if (i == 0) maxLeft = nums2[j - 1];
                else if (j == 0) maxLeft = nums1[i - 1];
                else maxLeft = Math.max(nums1[i - 1], nums2[j - 1]);
                if ((m + n) % 2 == 1) return maxLeft;
                int minRight;
                if (i == m) minRight = nums2[j];
                else if (j == n) minRight = nums1[i];
                else minRight = Math.min(nums1[i], nums2[j]);
                return (maxLeft + minRight) / 2.0;
            }
        }
        return 0.0;
    }
}

功能上与解法一完全等价,复杂度一样。

性能对比与选型

在 m=500, n=500 的随机有序数据上跑 10000 次的平均耗时(JDK 17):

解法平均时间 (ms)空间复杂度推荐场景
二分分割(解法一)0.0021O(1)O(log(min))面试首选,代码清晰
递归第 k 小0.0028O(log)O(log)理解递归思路
合并数组0.125O(m+n)O(m+n)小数据、写测试
迭代第 k 小0.0023O(1)O(log)追求极致性能
中位数性质二分0.0022O(1)O(log(min))喜欢另一种实现风格

实际面试中,解法一是标准答案,而且可以直接解释清楚分割条件的含义。解法二、四更适合需要不断查找不同 k 的场景(比如多次查询第 k 小)。如果只是解原题,解法一足够。

扩展:查找两个有序数组的第 k 小元素

原题求中位数本质就是找特定的 k。如果题目直接问任意 k,我们可以复用迭代版 findKth。例如:

public int findKthSortedArrays(int[] nums1, int[] nums2, int k) {
    return findKthIterative(nums1, nums2, k);
}

其中 findKthIterative 即解法四中的方法。这个扩展在数据库合并、流式处理中偶尔会遇到。

总结

  • 中位数问题转化为第 k 小是通用技巧,很多类似题目都可以套用。
  • 二分切割法(解法一)是最经典的 O(log(min(m,n))) 实现,务必要掌握。
  • 寻找第 k 小的方法更适合一般化问题,理解递归过程后迭代版更容易做空间优化。
  • 合并数组虽然不满足要求,但可以作为验证正确性的 baseline。
  • 实际写代码时注意数组越界和负无穷/正无穷的表示。

目录

  1. 问题
  2. 核心思路
  3. 解法一:二分分割(标准)
  4. 解法二:递归寻找第 k 小
  5. 解法三:合并数组(不满足要求)
  6. 解法四:迭代寻找第 k 小
  7. 解法五:中位数性质的二分
  8. 性能对比与选型
  9. 扩展:查找两个有序数组的第 k 小元素
  10. 总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • OpenCV 环境变量速查与调优指南
  • Apache SeaTunnel Web:我的数据集成心得
  • SpringBoot 住院管理系统的功能拆解与实现
  • 无人机远程识别(RemoteID)支持情况汇总
  • GitHub Copilot 账号切换与退出指南
  • LLaMA 模型动态库加载失败排查与修复指南
  • AI 入门者指南:常见术语解释与误区澄清
  • Transformer 核心机制解析:自注意力与多头机制
  • 基于 DeepSeek-OCR-WEBUI 的 OCR 技术实践:多语言与复杂场景支持
  • 相干伊辛机在医疗及医疗 AI 领域的应用前景
  • 云开发 Copilot:AI 重塑开发流程的实践指南
  • Dreamify 免费 AI 绘画工具的功能与实现
  • FPGA 基础概念与架构面试题解析(一)
  • 机器人动力学:牛顿欧拉法推导与详解
  • SpringBoot 源码解析:AnnotationConfigServletWebServerApplicationContext 构造流程
  • Spring Boot 数据导入导出与报表生成实战
  • 二分查找进阶:寻找峰值、旋转数组最小值与缺失数字
  • Linux diff 与 patch 命令实战指南
  • NVIDIA AI Playground 免费调用 GLM-4.7 与 MiniMax M2.1 模型指南
  • GitHub开源项目日报:AI代理与本地助手热榜 (2026-02-19)

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online