寻找两个正序数组的中位数:二分查找的终极算法

引言:中位数的「C位之争」

如果把数组比作排队买奶茶的队伍,中位数就是那个站在正中间的幸运儿——不需要知道所有人的位置,只需要找到那个「刚刚好」的中间位置。这个问题看似简单,却藏着算法世界的「效率密码」,尤其是当要求时间复杂度达到O(log(m+n))时,就像要求兔子不仅要跑赢比赛,还要跑出博尔特的速度!

解法等级划分

青铜解法:暴力合并的「小学生算法」

直观思路:把两个数组合并成一个大数组,像小学生合并作业本一样简单粗暴

func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
    // 合并两个有序数组(像把两堆牌合并成一堆)
    merged := make([]int, 0, len(nums1) + len(nums2))
    i, j := 0, 0
    
    // 双指针合并(像两个兔子赛跑,谁小谁先走)
    for i < len(nums1) && j < len(nums2) {
        if nums1[i] < nums2[j] {
            merged = append(merged, nums1[i])
            i++
        } else {
            merged = append(merged, nums2[j])
            j++
        }
    }
    // 处理剩余元素(总有一个兔子先跑到终点)
    merged = append(merged, nums1[i:]...)
    merged = append(merged, nums2[j:]...)
    
    // 计算中位数(中间位置的幸运儿)
    n := len(merged)
    if n % 2 == 1 {
        return float64(merged[n/2])
    }
    return float64(merged[n/2-1] + merged[n/2]) / 2
}

复杂度分析

  • 时间复杂度:O(m+n) - 两个兔子跑完了所有路程,像乌龟一样慢
  • 空间复杂度:O(m+n) - 额外数组存储所有元素,堪称「内存土豪」

青铜程序员的痛点:当m和n都是10⁴级别时,这个算法就像让蜗牛参加马拉松——LeetCode直接给你个超时大礼包!

提交结果,好像挺快的
在这里插入图片描述

白银解法:双指针的「优化版暴力」

优化思路:不需要完整合并数组,像聪明的兔子只跑一半路程

func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
    m, n := len(nums1), len(nums2)
    totalLength := m + n
    mid := totalLength / 2
    // 只需要记录中间位置附近的两个值
    prev, curr := 0, 0
    i, j := 0, 0
    
    for k := 0; k <= mid; k++ {
        prev = curr
        // 双指针移动(像两个兔子比赛,谁小谁前进)
        if i < m && (j >= n || nums1[i] < nums2[j]) {
            curr = nums1[i]
            i++
        } else {
            curr = nums2[j]
            j++
        }
    }
    
    if totalLength % 2 == 1 {
        return float64(curr)
    }
    return float64(prev + curr) / 2
}

复杂度分析

  • 时间复杂度:O((m+n)/2) - 兔子只跑了一半路程,比青铜快了一倍
  • 空间复杂度:O(1) - 只需要几个变量,堪称「空间洁癖患者福音」

边界情况专题:当一个数组为空时,就像其中一个兔子请假了,直接返回另一个数组的中位数

提交结果
在这里插入图片描述

黄金解法:二分查找的「精准制导」

核心思想:把问题转化为「寻找第k小的元素」,像用导弹精准打击目标位置

func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
    // 确保nums1是较短的数组,减少二分次数(像让短腿兔子先跑)
    if len(nums1) > len(nums2) {
        return findMedianSortedArrays(nums2, nums1)
    }
    m, n := len(nums1), len(nums2)
    totalLeft := (m + n + 1) / 2 // +1确保奇数时取左中位数
    
    left, right := 0, m
    for left <= right {
        // i: nums1中取前i个元素,j: nums2中取前j个元素
        i := left + (right - left) / 2
        j := totalLeft - i
        
        // 边界情况处理(兔子不能跑出跑道)
        nums1Left := math.MinInt32
        if i > 0 {
            nums1Left = nums1[i-1]
        }
        nums1Right := math.MaxInt32
        if i < m {
            nums1Right = nums1[i]
        }
        
        nums2Left := math.MinInt32
        if j > 0 {
            nums2Left = nums2[j-1]
        }
        nums2Right := math.MaxInt32
        if j < n {
            nums2Right = nums2[j]
        }
        
        // 二分调整(像调整望远镜焦距,直到看清目标)
        if nums1Left <= nums2Right && nums2Left <= nums1Right {
            // 找到合适的分割点
            if (m + n) % 2 == 1 {
                return float64(max(nums1Left, nums2Left))
            }
            return float64(max(nums1Left, nums2Left) + min(nums1Right, nums2Right)) / 2
        } else if nums1Left > nums2Right {
            // nums1取多了,左移
            right = i - 1
        } else {
            // nums2取多了,右移
            left = i + 1
        }
    }
    return 0
}

// 辅助函数:取最大值
func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

// 辅助函数:取最小值
func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

复杂度分析

  • 时间复杂度:O(log(min(m,n))) - 像兔子中的博尔特,速度快到模糊
  • 空间复杂度:O(1) - 极致优化,没有多余内存占用

黄金程序员的优雅:通过二分查找,我们把两个数组的问题转化为单一数组的查找问题,就像把复杂的爱情问题简化为"你喜欢我还是她"的选择题

提交结果
在这里插入图片描述

王者进阶:实战中的性能优化与扩展

内存优化:预分配数组容量,避免动态扩容

// 预分配足够容量,避免动态扩容
func findMedianSortedArraysOptimized(nums1 []int, nums2 []int) float64 {
    // ... 与黄金解法相同的核心逻辑 ...
    // 性能优化点:预先计算可能用到的变量
    totalLength := m + n
    isOdd := totalLength % 2 == 1
    // ...
}

并发安全版:在Go并发场景中使用互斥锁

import "sync"

var mu sync.Mutex

func findMedianSortedArraysConcurrent(nums1 []int, nums2 []int) float64 {
    mu.Lock()
    defer mu.Unlock()
    return findMedianSortedArrays(nums1, nums2)
}

算法迁移:二分思想的「七十二变」

二分查找就像算法世界的「瑞士军刀」,除了找中位数,还能解决:

  1. 寻找两个数组的第k小元素:把中位数问题推广到任意k值
  2. 分割数组的最大值最小化:像切披萨一样公平分配
  3. 在旋转排序数组中查找目标:旋转数组中的"寻宝游戏"

结语:算法优化的本质是「精准打击」

从青铜的O(m+n)到王者的O(log(min(m,n))),我们见证了算法优化的「进化史」。就像从步行到高铁的交通革命,每一次优化都是对「 unnecessary work 」的无情删减。

面试金句:当面试官问你如何优化两个数组的中位数查找时,你可以自信地说:“我用二分查找实现O(log(min(m,n)))复杂度,比题目要求的O(log(m+n))还快——就像兔子中的博尔特,不仅赢了比赛,还打破了世界纪录!”

算个球的算法哲学:算法优化的精髓,在于找到问题的「阿喀琉斯之踵」——有时候少即是多,简单即是美。

欢迎大家点赞,收藏,评论,转发,你们的支持是我最大的写作动力

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

GO兔

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

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

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

打赏作者

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

抵扣说明:

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

余额充值