算法题 寻找两个正序数组的中位数

4. 寻找两个正序数组的中位数

问题描述

给定两个大小分别为 mn 的正序(非递减)整数数组 nums1nums2,找出并返回这两个正序数组的中位数。要求算法的时间复杂度为 O(log(m+n))

示例

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3],中位数 2

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4],中位数 (2+3)/2 = 2.5

算法思路

二分查找法

  1. 核心思想
    将寻找中位数转化为寻找第 k 小的数(k 为中间位置)。对于两个有序数组,可以通过比较第 k/2 个元素,每次排除 k/2 个不可能的元素。
  2. 关键步骤
    • 若总长度 m+n 为奇数,中位数是第 (m+n+1)/2 小的数
    • 若为偶数,中位数是第 (m+n)/2(m+n)/2+1 小的数的平均值
  3. 查找第 k 小的数
    • 比较 nums1[k/2-1]nums2[k/2-1]
    • nums1[k/2-1] < nums2[k/2-1],排除 nums1 的前 k/2 个元素
    • 反之排除 nums2 的前 k/2 个元素
    • 递归处理剩余元素,更新 k = k - k/2
  4. 边界处理
    • 若一个数组为空,直接在另一个数组找第 k 小的数
    • k=1,返回两个数组首元素的较小值
    • 若数组长度不足 k/2,取末尾元素比较

代码实现

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int total = nums1.length + nums2.length;
        // 奇数:返回第 (total+1)/2 小的数
        if (total % 2 == 1) {
            return findKthElement(nums1, nums2, (total + 1) / 2);
        } 
        // 偶数:返回第 total/2 和 total/2+1 小的数的平均值
        else {
            double left = findKthElement(nums1, nums2, total / 2);
            double right = findKthElement(nums1, nums2, total / 2 + 1);
            return (left + right) / 2.0;
        }
    }

    /**
     * 在两个有序数组中查找第 k 小的元素
     * 
     * @param nums1 第一个有序数组
     * @param nums2 第二个有序数组
     * @param k 目标位置
     * @return 第 k 小的元素值
     */
    private double findKthElement(int[] nums1, int[] nums2, int k) {
        int index1 = 0, index2 = 0; // 当前查找的起始位置
        int len1 = nums1.length, len2 = nums2.length;
        
        while (true) {
            // 边界处理:一个数组已全部排除
            if (index1 == len1) return nums2[index2 + k - 1];
            if (index2 == len2) return nums1[index1 + k - 1];
            if (k == 1) return Math.min(nums1[index1], nums2[index2]); // k=1时直接返回最小值
            
            // 计算比较位置(处理数组长度不足的情况)
            int half = k / 2;
            int newIndex1 = Math.min(index1 + half, len1) - 1;
            int newIndex2 = Math.min(index2 + half, len2) - 1;
            
            // 比较并排除较小元素所在数组的前半部分
            if (nums1[newIndex1] <= nums2[newIndex2]) {
                k -= (newIndex1 - index1 + 1); // 更新剩余 k 值
                index1 = newIndex1 + 1;       // 更新数组1的起始位置
            } else {
                k -= (newIndex2 - index2 + 1);
                index2 = newIndex2 + 1;
            }
        }
    }
}

代码注释

代码部分说明
total % 2 == 1判断总长度是否为奇数
findKthElement(...)查找第 k 小的元素
index1, index2当前查找的起始位置
k == 1 处理返回两数组当前首元素的最小值
half = k / 2每次排除的元素数量
Math.min(index+half, len)-1防止数组越界
k -= (newIndex - index + 1)更新剩余查找长度
index = newIndex + 1更新数组起始位置

算法过程

nums1 = [1,3], nums2 = [2]

  1. 计算中位数位置
    • 总长度 3(奇数),k = (3+1)/2 = 2
  2. 查找第 2 小的数
    • index1=0, index2=0, k=2
    • half=1newIndex1=min(0+1,2)-1=0nums1[0]=1
    • newIndex2=min(0+1,1)-1=0nums2[0]=2
    • 1 < 2 → 排除 nums1[0]k=2-1=1, index1=1
    • k=1 → 返回 min(nums1[1], nums2[0])=min(3,2)=2

复杂度分析

  • 时间复杂度:O(log(m+n))
    每次循环排除约 k/2 个元素,k 初始值最大为 (m+n)/2,故时间复杂度为对数级
  • 空间复杂度:O(1)
    仅使用常数空间

关键点

  1. 问题转化
    将中位数问题转化为寻找第 k 小元素问题
  2. 二分排除
    通过比较第 k/2 个元素,每次排除 k/2 个不可能的元素
  3. 边界处理
    • 数组长度不足时取末尾元素
    • k=1 时直接返回最小值
    • 一个数组空时处理另一数组
  4. 高效性
    利用有序数组特性,避免完全合并数组

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 示例测试
    int[] nums1 = {1, 3};
    int[] nums2 = {2};
    System.out.println(solution.findMedianSortedArrays(nums1, nums2)); // 2.0
    
    int[] nums3 = {1, 2};
    int[] nums4 = {3, 4};
    System.out.println(solution.findMedianSortedArrays(nums3, nums4)); // 2.5
    
    // 一个数组为空
    int[] nums5 = {};
    int[] nums6 = {1};
    System.out.println(solution.findMedianSortedArrays(nums5, nums6)); // 1.0
    
    // 交叉数组
    int[] nums7 = {1, 3, 5};
    int[] nums8 = {2, 4, 6};
    System.out.println(solution.findMedianSortedArrays(nums7, nums8)); // 3.5
    
    // 大数测试
    int[] nums9 = {100000};
    int[] nums10 = {100001};
    System.out.println(solution.findMedianSortedArrays(nums9, nums10)); // 100000.5
}

常见问题

  1. 为什么比较 k/2 位置的元素?
    通过比较两个数组的第 k/2 个元素,可以确定至少一个数组的前 k/2 个元素不包含第 k 小的元素,从而高效排除。

  2. 如何处理数组长度不足 k/2
    取数组末尾元素进行比较,确保不会越界,且能正确排除有效元素。

  3. 为什么时间复杂度是 O(log(m+n))?
    每次排除约 k/2 个元素,k 值呈指数级减少,故时间复杂度为对数级。

  4. 能否合并数组后直接求中位数?
    合并数组需 O(m+n) 时间,不满足题目要求的 O(log(m+n)) 时间复杂度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值