Leetcode: 寻找两个正序数组的中位数

41 阅读4分钟

原题:leetcode.cn/problems/me…

题目描述

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

解题思路

核心思想

通过二分查找将问题转化为在两个有序数组中寻找第 k 小的数,其中 k 为中位数的位置。

关键步骤

  1. 统一处理奇偶情况
    • 若 m + n 为奇数,中位数是第 (m+n+1)/2 小的数。
    • 若 m + n 为偶数,中位数是第 (m+n)/2 和第 (m+n)/2 + 1 小的数的平均值。
  2. 二分查找策略
    • 比较 nums1 和 nums2 的前 k/2 个元素。
    • 较小部分的数组前 k/2 个元素不包含中位数,可排除。
  3. 边界条件处理
    • 当某个数组为空时,直接取另一个数组的第 k 个元素。
    • 当 k == 1 时,返回两个数组首元素的较小值。

复杂度分析

  • 时间复杂度:O(log⁡(m+n))O(log(m+n)),每次递归或迭代将 k 减半。
  • 空间复杂度:O(1)O(1),仅需常数空间。

代码实现

  • JavaScript
    var findMedianSortedArrays = function(nums1, nums2) {
        const m = nums1.length, n = nums2.length;
        const total = m + n;
        
        const findKth = (k) => {
            let i = 0, j = 0;
            while (true) {
                if (i === m) return nums2[j + k - 1];
                if (j === n) return nums1[i + k - 1];
                if (k === 1) return Math.min(nums1[i], nums2[j]);
                
                const new_i = Math.min(i + Math.floor(k / 2) - 1, m - 1);
                const new_j = Math.min(j + Math.floor(k / 2) - 1, n - 1);
                if (nums1[new_i] <= nums2[new_j]) {
                    k -= new_i - i + 1;
                    i = new_i + 1;
                } else {
                    k -= new_j - j + 1;
                    j = new_j + 1;
                }
            }
        };
        
        if (total % 2 === 1) {
            return findKth(Math.floor(total / 2) + 1);
        } else {
            return (findKth(total / 2) + findKth(total / 2 + 1)) / 2;
        }
    };
    
  • python
    def findMedianSortedArrays(nums1: list[int], nums2: list[int]) -> float:
        def find_kth(k):
            i, j = 0, 0
            while True:
                # 边界条件
                if i == m:
                    return nums2[j + k - 1]
                if j == n:
                    return nums1[i + k - 1]
                if k == 1:
                    return min(nums1[i], nums2[j])
                
                # 正常情况
                new_i = min(i + k // 2 - 1, m - 1)
                new_j = min(j + k // 2 - 1, n - 1)
                if nums1[new_i] <= nums2[new_j]:
                    k -= new_i - i + 1
                    i = new_i + 1
                else:
                    k -= new_j - j + 1
                    j = new_j + 1
        
        m, n = len(nums1), len(nums2)
        total = m + n
        if total % 2 == 1:
            return find_kth((total + 1) // 2)
        else:
            return (find_kth(total // 2) + find_kth(total // 2 + 1)) / 2
    
  • rust
    impl Solution {
        pub fn find_median_sorted_arrays(nums1: Vec<i32>, nums2: Vec<i32>) -> f64 {
            let m = nums1.len();
            let n = nums2.len();
            
            fn find_kth(nums1: &[i32], nums2: &[i32], k: usize) -> i32 {
                let m = nums1.len();
                let n = nums2.len();
                let mut i = 0;
                let mut j = 0;
                let mut k = k;
                
                loop {
                    if i == m {
                        return nums2[j + k - 1];
                    }
                    if j == n {
                        return nums1[i + k - 1];
                    }
                    if k == 1 {
                        return nums1[i].min(nums2[j]);
                    }
                    
                    let new_i = (i + k / 2 - 1).min(m - 1);
                    let new_j = (j + k / 2 - 1).min(n - 1);
                    if nums1[new_i] <= nums2[new_j] {
                        k -= new_i - i + 1;
                        i = new_i + 1;
                    } else {
                        k -= new_j - j + 1;
                        j = new_j + 1;
                    }
                }
            }
            
            let total = m + n;
            if total % 2 == 1 {
                find_kth(&nums1, &nums2, total / 2 + 1) as f64
            } else {
                let left = find_kth(&nums1, &nums2, total / 2);
                let right = find_kth(&nums1, &nums2, total / 2 + 1);
                (left + right) as f64 / 2.0
            }
        }
    }
    

实际应用场景

  1. 数据库查询优化:合并两个有序结果集时快速计算中位数。
  2. 实时数据流处理:动态统计多个有序数据源的中位数(需结合其他数据结构)。
  3. 财务分析:计算两支股票价格序列的中位数波动率。
  4. 生物信息学:比对两组基因测序数据的中间值差异。

相似题目

  1. 合并两个有序数组 解题思路:双指针合并,但时间复杂度为 O(m+n)O(m+n),不满足本题要求。
  2. 数据流的中位数 解题思路:使用堆动态维护中位数,适用于数据流场景。
  3. 第 k 个最大的元素 解题思路:快速选择算法,但针对无序数组。