三步搞定:双指针归并法求两个有序数组的中位数(Java 实现)

引言

在算法面试和刷题中,"寻找两个有序数组的中位数"是 LeetCode 上的经典难题。本文将以新手友好的方式,带你用双指针归并法高效解决这一问题,提供清晰的思路、完整的 Java 代码和复杂度分析,助你轻松应对面试和实战场景。

问题描述

给定两个有序数组 nums1 和 nums2,求它们合并后的中位数。示例源自 LeetCode:LeetCode 4. Median of Two Sorted Arrays

解题思路:双指针归并法

核心思路

  1. 使用两个指针分别遍历两个数组,按从小到大的顺序合并到新数组。
  2. 合并完成后,根据合并后数组长度的奇偶性直接计算中位数。
  3. 算法时间复杂度为 O(m+n),实现简单易懂。

实现简单,时间复杂度 O(m+n)。如需 O(log(m+n)),可采用二分查找优化。

Java 完整代码(含详细注释)

java 复制代码
class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        // 1. 创建一个辅助数组,长度为两个原数组长度之和,用于存放合并后的有序序列
        int [] nums = new int[n+m];
        double res = 0;
        
        // i 指向 nums1 的当前处理位置,j 指向 nums2,k 指向新数组 nums
        int i = 0, j = 0, k = 0;

        // 2. 归并过程:比较两个数组当前的元素,谁小就先放谁进新数组
        while (i < m && j < n){
            if (nums1[i] < nums2[j]){
                nums[k] = nums1[i]; // nums1 当前值更小,放入新数组
                i++; // nums1 指针后移
            } else {
                nums[k] = nums2[j]; // nums2 当前值更小(或相等),放入新数组
                j++; // nums2 指针后移
            }
            k++; // 新数组指针永远后移
        }

        // 3. 扫尾工作:如果 nums1 还有剩余元素,直接按顺序全部拼接到新数组后面
        while (i < m){
            nums[k] = nums1[i];
            i++;
            k++;
        }

        // 4. 扫尾工作:如果 nums2 还有剩余元素,直接按顺序全部拼接到新数组后面
        while (j < n) {
            nums[k] = nums2[j];
            j++;
            k++;
        }

        // 5. 计算中位数:根据合并后大数组总长度的奇偶性来判断
        if((n + m) % 2 != 0 ){
            // 如果是奇数,中位数就是中间那个数
            // 例如长度为 5,下标 0,1,2,3,4,中位数下标为 5/2 = 2
            res = (double) nums[(n+m)/2];
        } else {
            // 如果是偶数,中位数是中间两个数的平均值
            // 例如长度为 4,下标 0,1,2,3,中位数为 (nums[1] + nums[2]) / 2
            res = (double) (nums[(n+m)/2] + nums[(n+m)/2-1]) / 2;
        }

        return res;
    }
}

复杂度分析

  • 时间复杂度:O(m+n)(m、n 为两个数组长度)。
  • 空间复杂度:O(m+n)(需新数组存储归并结果)。

进阶优化

本方法实现简单、易于理解,适合初学者和面试快速上手。如果追求更高效率,可研究二分查找优化至 O(log(m+n)),欢迎在评论区交流你的优化思路!

拓展二分查找

思路:

  • 转化问题:在两个有序数组中寻找中位数,本质上是寻找一个划分点,将 nums1 和 nums2分别划分为左右两部分。

  • 核心条件

    • 左半部分的长度等于右半部分的长度(或比右半部分多 1)。

    • 左半部分的最大值≤ 右半部分的最小值,即:max(left_A, left_B) <= min(right_A, right_B)

  • 优化搜索:由于数组是有序的,我们只需要在较短的数组上进行二分查找。如果短数组的划分点确定了,长数组的划分点为了满足"长度相等"也就自动确定了。

解题过程

  • 确保 m ≤ n:如果 nums1 较长,交换两者。这能保证二分查找的时间复杂度为 O(log(min(m, n)))。

  • 设定二分范围 :在 [0, m] 之间搜索nums1的划分位置 i。

  • 计算对应位置

    • i 是 nums1 的划分点,j = (m + n + 1) / 2 - i 是 nums2的划分点。
  • 二分判断

    • 如果 nums1[i-1] nums2[j],说明 i太大了,需要向左收缩。

    • 如果nums2[j-1] > nums1i,说明 i太小了,需要向右扩张。

  • 处理边界 :考虑到划分点可能在数组的两端(0或 m),使用 Integer.MIN_VALUEInteger.MAX_VALUE 来简化边界判断。

  • 计算结果

    • 总长度为奇数:返回左半部分的最大值。

    • 总长度为偶数:返回(左半部分最大值 + 右半部分最小值)/ 2。

复杂度

  • 时间复杂度: O(log(min(m, n)))。我们在较短的数组上进行二分查找。

  • 空间复杂度: O(1)。只使用了常数级别的额外变量。

代码

java 复制代码
class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        // 确保 nums1 是较短的数组,优化效率
        if (nums1.length > nums2.length) {
            return findMedianSortedArrays(nums2, nums1);
        }

        int m = nums1.length;
        int n = nums2.length;
        int left = 0, right = m;
        
        // 整个左半部分需要的元素个数
        int totalLeft = (m + n + 1) / 2;

        while (left < right) {
            // 二分查找 nums1 的划分点 i
            int i = left + (right - left + 1) / 2;
            int j = totalLeft - i;

            // 如果 nums1[i-1] > nums2[j],说明 i 划分得太靠右了
            if (nums1[i - 1] > nums2[j]) {
                right = i - 1;
            } else {
                left = i;
            }
        }

        int i = left;
        int j = totalLeft - i;

        // 边界处理:左侧最大值
        int nums1LeftMax = (i == 0) ? Integer.MIN_VALUE : nums1[i - 1];
        int nums2LeftMax = (j == 0) ? Integer.MIN_VALUE : nums2[j - 1];
        
        // 边界处理:右侧最小值
        int nums1RightMin = (i == m) ? Integer.MAX_VALUE : nums1[i];
        int nums2RightMin = (j == n) ? Integer.MAX_VALUE : nums2[j];

        if ((m + n) % 2 != 0) {
            // 奇数长度,直接返回左半部分最大值
            return Math.max(nums1LeftMax, nums2LeftMax);
        } else {
            // 偶数长度,取左侧最大和右侧最小的平均值
            return (Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin)) / 2.0;
        }
    }
}
相关推荐
小bo波14 小时前
Java Swing 图形用户界面实验 —— 从算术练习到游戏开发的完整实践
java·课程设计·gui·游戏开发·扫雷·swing
咖啡八杯15 小时前
GoF设计模式——备忘录模式
java·后端·spring·设计模式
HjhIron19 小时前
面试常客:字符串算法从入门到进阶
算法·面试
吴佳浩20 小时前
DeepSeek DSpark:Confidence-Scheduled Speculative Decoding 技术解析
人工智能·算法·deepseek
触底反弹1 天前
🧠 搞懂 Token,才算真正入门大模型——从分词原理到 Embedding 语义实战
javascript·人工智能·算法
SamDeepThinking1 天前
裁掉那个差程序员后,给你看团队里高手的代码:这个习惯,希望你有
java·后端·程序员
vivo互联网技术1 天前
ICLR 2026 | 基于后验采样的图像恢复方法LearnIR:人脸去阴影、去雾
人工智能·算法·aigc
朕瞧着你甚好1 天前
技术雷达 & Java 集成评估报告 — Apache Tika 3.3.1
java·ai编程
浮生望1 天前
JS字符串与回文算法:从包装类到双指针的面试进阶之路
javascript·算法
黄敬峰1 天前
面试必刷:从JS底层包装类到双指针,彻底搞懂字符串与回文算法
算法