LeetCode 第4题:寻找两个正序数组的中位数

LeetCode 第4题:寻找两个正序数组的中位数

题目描述

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数。

算法的时间复杂度应该为 O(log (m+n))。

难度

困难

题目链接

https://leetcode.cn/problems/median-of-two-sorted-arrays/

示例

示例 1:

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

示例 2:

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

提示

  • nums1.length == m
  • nums2.length == n
  • 0 <= m <= 1000
  • 0 <= n <= 1000
  • 1 <= m + n <= 2000
  • -106 <= nums1[i], nums2[i] <= 106

解题思路

方法一:二分查找

要达到 O(log(m+n)) 的时间复杂度,必须使用二分查找。关键是将问题转化为寻找第k小的数的问题。

关键点:

  1. 中位数的定义:
    • 当总长度为奇数时,中位数是第 (m+n)/2 + 1 个数
    • 当总长度为偶数时,中位数是第 (m+n)/2 和第 (m+n)/2 + 1 个数的平均值
  2. 使用二分查找来寻找第k小的数
  3. 每次比较两个数组的第 k/2 个数,排除掉不可能的部分

具体步骤:

  1. 确定中位数的位置k
  2. 在两个数组中进行二分查找:
    • 比较两个数组中第 k/2 个数的大小
    • 较小的那部分不可能包含第k小的数
    • 排除较小的部分,继续查找剩余部分
  3. 递归进行上述过程,直到找到目标数

时间复杂度:O(log(m+n))
空间复杂度:O(1)

方法二:划分数组(更优解)

这个方法的思路是将两个数组分别划分,使得:

  • 左半部分的长度等于右半部分
  • 左半部分的最大值小于等于右半部分的最小值

代码实现

C# 实现(二分查找)

public class Solution {
    public double FindMedianSortedArrays(int[] nums1, int[] nums2) {
        int totalLength = nums1.Length + nums2.Length;
        if (totalLength % 2 == 1) {
            return FindKth(nums1, 0, nums2, 0, totalLength / 2 + 1);
        } else {
            return (FindKth(nums1, 0, nums2, 0, totalLength / 2) + 
                   FindKth(nums1, 0, nums2, 0, totalLength / 2 + 1)) / 2.0;
        }
    }
  
    private double FindKth(int[] nums1, int start1, int[] nums2, int start2, int k) {
        // 如果数组1已经用完,直接从数组2中返回第k个数
        if (start1 >= nums1.Length) {
            return nums2[start2 + k - 1];
        }
        // 如果数组2已经用完,直接从数组1中返回第k个数
        if (start2 >= nums2.Length) {
            return nums1[start1 + k - 1];
        }
        // 如果k=1,返回两个数组开头较小的数
        if (k == 1) {
            return Math.Min(nums1[start1], nums2[start2]);
        }
      
        // 比较两个数组中第k/2个数的大小
        int mid1 = start1 + k/2 - 1 < nums1.Length ? 
                  nums1[start1 + k/2 - 1] : int.MaxValue;
        int mid2 = start2 + k/2 - 1 < nums2.Length ? 
                  nums2[start2 + k/2 - 1] : int.MaxValue;
      
        if (mid1 < mid2) {
            // 排除nums1的前k/2个数
            return FindKth(nums1, start1 + k/2, nums2, start2, k - k/2);
        } else {
            // 排除nums2的前k/2个数
            return FindKth(nums1, start1, nums2, start2 + k/2, k - k/2);
        }
    }
}

代码详解

  1. totalLength % 2 == 1:判断总长度是奇数还是偶数
  2. FindKth方法:查找第k小的数
    • 处理边界情况:某个数组用完或k=1
    • 比较两个数组中第k/2个数的大小
    • 排除较小的部分,继续递归查找
  3. int.MaxValue:处理数组长度不足k/2的情况
  4. 递归调用时更新起始位置和k值

执行结果

  • 执行用时:92 ms
  • 内存消耗:51.2 MB

总结与反思

  1. 这是一道经典的困难题目,考察了:
    • 二分查找的应用
    • 分治思想
    • 边界情况的处理
  2. 关键点:
    • 理解中位数的定义
    • 正确处理奇偶长度的情况
    • 高效排除不可能的部分
  3. 优化思路:
    • 可以通过划分数组的方法进一步优化
    • 注意特殊情况的处理

相关题目

  • LeetCode 第215题:数组中的第K个最大元素
  • LeetCode 第295题:数据流的中位数
  • LeetCode 第378题:有序矩阵中第K小的元素
  • LeetCode 第973题:最接近原点的K个点
posted @ 2025-02-08 22:36  旧厂街小江  阅读(46)  评论(0)    收藏  举报