力扣hot100—系列5-堆

堆的特点是能以 O(log⁡N)O(\log N)O(logN) 的时间复杂度完成插入和删除,并以 O(1)O(1)O(1) 的时间获取最大值或最小值。


1. 数组中的第 K 个最大元素

核心思想:维护一个大小为 KKK 的小顶堆。

  • 直观思路:
    • 如果我们想找第 KKK 大的数,最简单的是全排序,但代价太高。
    • 想象一个容器,它只装前 KKK 个最大的数。 当新来一个数时,如果它比容器里最小的那个还要大,就把容器里最小的踢出去,把它放进去。
    • 这个"容器"就是小顶堆 。堆顶永远是这 KKK 个数里最小的那一个,也就是全局第 KKK 大的数。
  • 复杂度: 时间 O(Nlog⁡K)O(N \log K)O(NlogK),空间 O(K)O(K)O(K)。

代码实现 (Python):

python 复制代码
import heapq

def findKthLargest(nums, k):
    # 初始化一个空堆
    min_heap = []
    for num in nums:
        # 将当前数字压入堆中
        heapq.heappush(min_heap, num)
        # 如果堆的大小超过了 k,弹出堆顶(最小的那个)
        if len(min_heap) > k:
            heapq.heappop(min_heap)
    
    # 遍历结束后,堆中剩下的就是前 k 个最大的数
    # 堆顶就是这 k 个数中最小的,即第 k 个最大的数
    return min_heap[0]

2. 前 K 个高频元素

核心思想:哈希表计数 + 维护大小为 KKK 的小顶堆。

  • 直观思路:
    1. 先用字典(哈希表)统计每个数字出现的频率
    2. 和上一题类似,我们找"频率最高"的 KKK 个。
    3. 建立一个小顶堆,但堆里存的是 (频率, 元素)。堆根据频率排序,保持堆的大小为 KKK。
    4. 最后堆里剩下的 KKK 个元素就是结果。
  • 复杂度: 时间 O(Nlog⁡K)O(N \log K)O(NlogK),空间 O(N)O(N)O(N)。

代码实现 (Python):

python 复制代码
import heapq
from collections import Counter

def topKFrequent(nums, k):
    # 1. 统计频率 {数字: 出现次数}
    count = Counter(nums)
    
    # 2. 维护大小为 k 的小顶堆
    min_heap = []
    for num, freq in count.items():
        # 注意:heapq 默认根据元组的第一个值(freq)排序
        heapq.heappush(min_heap, (freq, num))
        if len(min_heap) > k:
            heapq.heappop(min_heap)
            
    # 3. 提取结果
    return [item[1] for item in min_heap]

3. 数据流的中位数

核心思想:对顶堆(大顶堆 + 小顶堆)。

  • 直观思路:
    • 中位数把有序序列分成左右两半:左半部分都小于中位数,右半部分都大于中位数。
    • 左半部分: 我们需要左边最大 的那个数,用 大顶堆(SmallHalf)。
    • 右半部分: 我们需要右边最小 的那个数,用 小顶堆(LargeHalf)。
    • 规则:
      1. 让大顶堆的数量 ≥\ge≥ 小顶堆的数量(最多多 1 个)。
      2. 如果有奇数个数,中位数就是大顶堆的堆顶。
      3. 如果有偶数个数,中位数就是两个堆顶的平均值。
  • 难点(如何添加数): 为了保证平衡,新数先加进大顶堆,再把大顶堆最大的那个挪到小顶堆,如果小顶堆人多了,再挪回大顶堆。

代码实现 (Python):

python 复制代码
import heapq

class MedianFinder:
    def __init__(self):
        # Python 只有小顶堆,实现大顶堆需将数值取负
        self.small_half = []  # 大顶堆 (存较小的一半)
        self.large_half = []  # 小顶堆 (存较大的一半)

    def addNum(self, num: int) -> None:
        # 1. 先把数放进大顶堆(取负存入)
        heapq.heappush(self.small_half, -num)
        
        # 2. 确保左边最大的数搬运到右边
        # 将大顶堆的堆顶弹出,放入小顶堆
        heapq.heappush(self.large_half, -heapq.heappop(self.small_half))
        
        # 3. 平衡数量:如果小顶堆人多,搬回大顶堆
        if len(self.large_half) > len(self.small_half):
            heapq.heappush(self.small_half, -heapq.heappop(self.large_half))

    def findMedian(self) -> float:
        if len(self.small_half) > len(self.large_half):
            return -self.small_half[0]
        else:
            return (-self.small_half[0] + self.large_half[0]) / 2.0

刷题建议:

  1. 为什么用小顶堆求"最大 K 个"?
    因为你需要不断淘汰掉当前"前 K 名"里最弱的(最小的),所以要把最小的放在堆顶方便踢出去。
  2. Python 的 heapq 默认是小顶堆。
    如果你需要大顶堆,最简单的办法是给所有数字乘以 -1,取出时再乘回来。
  3. 对顶堆是处理动态中位数的"大杀器"。
    它的本质是用两个堆模拟了一个有序数组的中间分界点。
相关推荐
CoovallyAIHub12 小时前
Moonshine:比 Whisper 快 100 倍的端侧语音识别神器,Star 6.6K!
深度学习·算法·计算机视觉
CoovallyAIHub13 小时前
速度暴涨10倍、成本暴降6倍!Mercury 2用扩散取代自回归,重新定义LLM推理速度
深度学习·算法·计算机视觉
CoovallyAIHub13 小时前
实时视觉AI智能体框架来了!Vision Agents 狂揽7K Star,延迟低至30ms,YOLO+Gemini实时联动!
算法·架构·github
CoovallyAIHub14 小时前
开源:YOLO最强对手?D-FINE目标检测与实例分割框架深度解析
人工智能·算法·github
CoovallyAIHub14 小时前
OpenClaw:从“19万星标”到“行业封杀”,这只“赛博龙虾”究竟触动了谁的神经?
算法·架构·github
刀法如飞14 小时前
程序员必须知道的核心算法思想
算法·编程开发·算法思想
徐小夕16 小时前
pxcharts Ultra V2.3更新:多维表一键导出 PDF,渲染兼容性拉满!
vue.js·算法·github
CoovallyAIHub17 小时前
OpenClaw一脚踩碎传统CV?机器终于不再只是看世界
深度学习·算法·计算机视觉
CoovallyAIHub17 小时前
仅凭单目相机实现3D锥桶定位?UNet-RKNet破解自动驾驶锥桶检测难题
深度学习·算法·计算机视觉