力扣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. 对顶堆是处理动态中位数的"大杀器"。
    它的本质是用两个堆模拟了一个有序数组的中间分界点。
相关推荐
寻寻觅觅☆13 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
偷吃的耗子14 小时前
【CNN算法理解】:三、AlexNet 训练模块(附代码)
深度学习·算法·cnn
化学在逃硬闯CS14 小时前
Leetcode1382. 将二叉搜索树变平衡
数据结构·算法
ceclar12315 小时前
C++使用format
开发语言·c++·算法
Gofarlic_OMS15 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
夏鹏今天学习了吗15 小时前
【LeetCode热题100(100/100)】数据流的中位数
算法·leetcode·职场和发展
忙什么果16 小时前
上位机、下位机、FPGA、算法放在哪层合适?
算法·fpga开发
董董灿是个攻城狮16 小时前
AI 视觉连载4:YUV 的图像表示
算法
ArturiaZ17 小时前
【day24】
c++·算法·图论
大江东去浪淘尽千古风流人物17 小时前
【SLAM】Hydra-Foundations 层次化空间感知:机器人如何像人类一样理解3D环境
深度学习·算法·3d·机器人·概率论·slam