[力扣]给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回[leetcode 004]

4.高效查找两个正序数组的中位数:O(log(m+n))算法详解

问题引入

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

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

例题4

中位数的定义与问题边界

中位数是将数据分为两部分的中间值,需满足:

  1. 长度平衡:左右两部分长度相等或相差1
  2. 数值有序:左部所有元素 ≤ 右部所有元素

对于正序数组,无需合并即可高效查找中位数。输出规则:

  • 奇数长度:中间位置的单个数值
  • 偶数长度:中间两个数的平均值(需保留浮点数精度)

时间复杂度的严格限制

当总长度为1e6时:

  • 暴力解法(O(m+n)):需100万次操作
  • 二分查找(O(log(m+n))):仅需约20次操作

这种差距在大数据场景下至关重要,必须利用数组有序性实现对数级复杂度。

示例驱动的直观理解

示例1nums1 = [1,3], nums2 = [2]
合并后[1,2,3],中位数为2(奇数长度)

示例2nums1 = [1,2], nums2 = [3,4]
合并后[1,2,3,4],中位数为(2+3)/2=2.5(偶数长度)

合并数组法虽然直观,但存在O(m+n)的时间和空间开销,必须寻找更优解。

从暴力解法到二分查找的思路演进

暴力解法:合并数组法的局限

合并过程类似归并排序:

def merge_and_find(nums1, nums2):
    merged = []
    i = j = 0
    while i < len(nums1) and j < len(nums2):
        if nums1[i] < nums2[j]:
            merged.append(nums1[i])
            i += 1
        else:
            merged.append(nums2[j])
            j += 1
    merged.extend(nums1[i:])
    merged.extend(nums2[j:])
    L = len(merged)
    return merged[L//2] if L%2 else (merged[L//2-1]+merged[L//2])/2

缺陷

  • 时间复杂度O(m+n),空间复杂度O(m+n)
  • 大数据场景下内存占用和运算时间不可接受

关键洞察:分割数组找中位数

核心思想:找到分割点(i,j),使:

  • 左部元素总数 = (m+n+1)//2(确保左部可能多1个元素)
  • 左部最大值 ≤ 右部最小值

此时:

  • 奇数长度:中位数 = 左部最大值
  • 偶数长度:中位数 = (左部最大值+右部最小值)/2

二分查找的引入

选择较短数组进行二分(时间复杂度O(log min(m,n))):

  • 分割点i(nums1)和j(nums2)满足i+j=(m+n+1)//2
  • 通过二分调整i,使nums1[i-1] ≤ nums2[j]且nums2[j-1] ≤ nums1[i]

二分查找算法的核心原理

分割点的数学约束

设总长度L=m+n,左部需有(L+1)//2个元素,因此:
i + j = (m + n + 1) // 2

示例验证

  • 示例1:m=2,n=1 → i+j=(3+1)//2=2
  • 示例2:m=2,n=2 → i+j=(4+1)//2=2

分割条件与调整策略

核心条件

  • 左部最大值 = max(nums1[i-1], nums2[j-1])
  • 右部最小值 = min(nums1[i], nums2[j])
  • 需满足:左部最大值 ≤ 右部最小值

调整规则

  • 若nums1[i-1] > nums2[j] → i过大,需左移
  • 若nums2[j-1] > nums1[i] → i过小,需右移

边界处理与哨兵值技巧

使用±∞处理边界情况:

  • i=0(nums1左部为空)→ nums1[i-1] = -∞
  • i=m(nums1右部为空)→ nums1[i] = +∞
  • j=0或j=n时同理

代码实现

max_left = max(nums1[i-1] if i>0 else -inf, nums2[j-1] if j>0 else -inf)
min_right = min(nums1[i] if i<m else inf, nums2[j] if j<n else inf)

Python代码实现与关键细节

完整代码与注释

def findMedianSortedArrays(nums1, nums2):
    # 确保nums1是较短数组
    if len(nums1) > len(nums2):
        nums1, nums2 = nums2, nums1
    m, n = len(nums1), len(nums2)
    left, right = 0, m  # 二分查找范围
    
    while left <= right:
        i = (left + right) // 2  # nums1分割点
        j = (m + n + 1) // 2 - i  # nums2分割点
        
        # 处理边界情况
        nums1_left_max = nums1[i-1] if i > 0 else float('-inf')
        nums1_right_min = nums1[i] if i < m else float('inf')
        nums2_left_max = nums2[j-1] if j > 0 else float('-inf')
        nums2_right_min = nums2[j] if j < n else float('inf')
        
        # 判断分割是否有效
        if nums1_left_max <= nums2_right_min and nums2_left_max <= nums1_right_min:
            # 计算中位数
            if (m + n) % 2 == 1:
                return max(nums1_left_max, nums2_left_max)
            else:
                return (max(nums1_left_max, nums2_left_max) + 
                        min(nums1_right_min, nums2_right_min)) / 2
        elif nums1_left_max > nums2_right_min:
            right = i - 1  # i过大,左移
        else:
            left = i + 1  # i过小,右移
    
    raise ValueError("Input arrays are not sorted")

关键细节解析

  1. 数组交换:确保在较短数组上二分,优化时间复杂度
  2. 分割点计算:i和j的关系保证左右部长度平衡
  3. 哨兵值:用±∞简化边界判断,避免索引越界
  4. 二分调整:通过比较边界值动态调整i,找到合法分割点

示例验证与执行流程

示例1:奇数长度

输入:nums1=[1,3], nums2=[2]
预处理:交换后nums1=[2], nums2=[1,3](m=1,n=2)
二分过程

  • i=(0+1)//2=0 → j=2-0=2
  • max_left = max(-inf, nums2[1])=3? 修正后nums2=[1,2] → max_left=2
  • min_right = min(2, inf)=2
  • 中位数=2(奇数长度)

示例2:偶数长度

输入:nums1=[1,2], nums2=[3,4](m=2,n=2)
二分过程

  • i=(0+2)//2=1 → j=2-1=1
  • nums1_left_max=1, nums2_right_min=4(1≤4)
  • nums2_left_max=3, nums1_right_min=2(3>2 → i过小)
  • left=2 → i=2, j=0
  • max_left=2, min_right=3 → 中位数=(2+3)/2=2.5

算法复杂度分析

时间复杂度:O(log min(m,n))

  • 在较短数组上二分,迭代次数为log min(m,n)
  • min(m,n) ≤ m+n → log min(m,n) ≤ log(m+n),满足题目要求

空间复杂度:O(1)

  • 仅使用常数级变量,不依赖输入规模
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

韩进520

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值