三步搞定:双指针归并法求两个有序数组的中位数(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] > nums1[i],说明 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;
        }
    }
}
相关推荐
程序员欣宸2 小时前
LangChain4j实战之十四:函数调用,高级API版本
java·ai·langchain4j
星期五不见面2 小时前
机器人学习!(二)ROS-模型优化与加速(TensorRT)(4)2026/01/15
学习
人工干智能2 小时前
python的高级技巧:Pandas中的`iloc[]`和`loc[]`
开发语言·python·pandas
wjs20242 小时前
Chart.js 混合图:深入解析与实战指南
开发语言
专注VB编程开发20年2 小时前
MQTT傻瓜化调用组件,零成本学习.NET开发,上位机开发
学习·机器学习·.net
码头整点薯条2 小时前
License 集成 Spring Gateway:解决 WebFlux 非阻塞与 Spring MVC Servlet 阻塞兼容问题
java·spring
bing.shao2 小时前
基于 Go + Ollama 开发智能日志分析工具完整实战
开发语言·后端·golang
白露与泡影2 小时前
Spring 的西西弗斯之石:理解 BeanFactory、FactoryBean 与 ObjectFactory
java·后端·spring
忧郁的Mr.Li2 小时前
Spring+Mybatis配置自定义线程事务管理
java·spring·mybatis