三步搞定:双指针归并法求两个有序数组的中位数(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;
        }
    }
}
相关推荐
2601_949146535 分钟前
Python语音通知API示例代码汇总:基于Requests库的语音接口调用实战
开发语言·python
3GPP仿真实验室11 分钟前
【Matlab源码】6G候选波形:OFDM-IM 索引调制仿真平台
开发语言·matlab
计算机学姐20 分钟前
基于SpringBoot的校园社团管理系统
java·vue.js·spring boot·后端·spring·信息可视化·推荐算法
葵花楹23 分钟前
【算法题】【动态规划2】【背包动态规划】
算法·动态规划
数研小生24 分钟前
1688商品列表API:高效触达批发电商海量商品数据的技术方案
大数据·python·算法·信息可视化·json
云小逸26 分钟前
【nmap源码学习】 Nmap 源码深度解析:nmap_main 函数详解与 NSE 脚本引擎原理
网络协议·学习·安全
Coder_Boy_27 分钟前
基于SpringAI的在线考试系统-企业级教育考试系统核心架构(完善版)
开发语言·人工智能·spring boot·python·架构·领域驱动
java1234_小锋28 分钟前
Java高频面试题:SpringBoot如何自定义Starter?
java·spring boot·面试
落霞的思绪29 分钟前
Spring AI Alibaba 集成 Redis 向量数据库实现 RAG 与记忆功能
java·spring·rag·springai
键盘帽子29 分钟前
长连接中异步任务的同步等待陷阱:一次主线程阻塞的排查与修复
java·websocket·java-ee·web