堆 / 优先队列专题二刷笔记:前 K 个高频元素 & 数据流的中位数

目录

[一、LeetCode 347. 前 K 个高频元素(中等)](#一、LeetCode 347. 前 K 个高频元素(中等))

题目描述

核心思路

[方法 1:小顶堆(推荐,时间复杂度 O (n log k))](#方法 1:小顶堆(推荐,时间复杂度 O (n log k)))

[方法 2:大顶堆(写法简单,但效率略低)](#方法 2:大顶堆(写法简单,但效率略低))

[代码实现(Java 小顶堆版)](#代码实现(Java 小顶堆版))

二刷复盘要点

[二、LeetCode 295. 数据流的中位数(困难)](#二、LeetCode 295. 数据流的中位数(困难))

题目描述

核心思路:双堆法(经典模板)

代码实现(Java)

二刷复盘要点

三、两道题的核心模板总结


这两道题是堆(优先队列)的「面试双杀」,也是我自己二刷时的重点复盘题,帮你把核心模板和易错点一次性吃透。


一、LeetCode 347. 前 K 个高频元素(中等)

题目描述

给你一个整数数组 nums 和一个整数 k,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

核心思路

这道题的核心是 **「统计频率 + 用堆筛选前 K 大」**,二刷时一定要掌握两种写法的区别:

方法 1:小顶堆(推荐,时间复杂度 O (n log k))
  1. 统计频率:用 HashMap 统计每个数字出现的次数。
  2. 维护小顶堆 :堆的大小始终不超过 k。遍历所有频率,当堆大小小于 k 时直接入堆;当堆大小等于 k 时,如果当前频率大于堆顶元素,则弹出堆顶、压入当前元素。
  3. 结果收集 :遍历结束后,堆中剩下的 k 个元素就是频率最高的前 k 个。
方法 2:大顶堆(写法简单,但效率略低)

直接把所有元素按频率压入大顶堆,然后弹出前 k 个即可。但时间复杂度为 O (n log n),在数据量大时效率不如小顶堆。

代码实现(Java 小顶堆版)

java

运行

复制代码
public int[] topKFrequent(int[] nums, int k) {
    // 1. 统计频率
    Map<Integer, Integer> freqMap = new HashMap<>();
    for (int num : nums) {
        freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
    }

    // 2. 小顶堆:按频率升序排列,堆顶是当前频率最小的元素
    PriorityQueue<Map.Entry<Integer, Integer>> minHeap = 
        new PriorityQueue<>(Comparator.comparingInt(Map.Entry::getValue));

    for (Map.Entry<Integer, Integer> entry : freqMap.entrySet()) {
        if (minHeap.size() < k) {
            minHeap.offer(entry);
        } else {
            if (entry.getValue() > minHeap.peek().getValue()) {
                minHeap.poll();
                minHeap.offer(entry);
            }
        }
    }

    // 3. 收集结果
    int[] res = new int[k];
    int idx = 0;
    while (!minHeap.isEmpty()) {
        res[idx++] = minHeap.poll().getKey();
    }
    return res;
}

二刷复盘要点

  • 为什么用小顶堆而不是大顶堆?因为小顶堆的大小始终是 k,堆操作的时间复杂度是 O (log k),整体效率更高。
  • 易错点:堆的比较器要写对,小顶堆是按频率升序,而不是按元素本身的值排序。

二、LeetCode 295. 数据流的中位数(困难)

题目描述

中位数是有序列表中间的数。如果列表长度是偶数,中位数是中间两个数的平均值。设计一个支持 addNumfindMedian 操作的数据结构:

  • addNum(int num) 从数据流中添加一个整数到数据结构中。
  • findMedian() 返回目前所有元素的中位数。

核心思路:双堆法(经典模板)

用两个堆分别维护数据的左右两半:

  1. 大顶堆(left):存储前半部分数据,堆顶是前半部分的最大值。
  2. 小顶堆(right):存储后半部分数据,堆顶是后半部分的最小值。

维护规则

  • 大顶堆的大小要么和小顶堆相等,要么比小顶堆多 1(保证奇数长度时,大顶堆的堆顶就是中位数)。
  • 每次添加元素时,先根据元素大小判断该加入哪个堆,再调整两个堆的大小,保持平衡。

代码实现(Java)

java

运行

复制代码
class MedianFinder {
    // 大顶堆:存储前半部分,堆顶是前半部分的最大值
    private PriorityQueue<Integer> left;
    // 小顶堆:存储后半部分,堆顶是后半部分的最小值
    private PriorityQueue<Integer> right;

    public MedianFinder() {
        left = new PriorityQueue<>((a, b) -> b - a);
        right = new PriorityQueue<>();
    }

    public void addNum(int num) {
        // 先决定加入哪个堆
        if (left.isEmpty() || num <= left.peek()) {
            left.offer(num);
        } else {
            right.offer(num);
        }

        // 调整堆的大小,保持平衡
        // 情况1:大顶堆比小顶堆多2个,需要把大顶堆的堆顶移到小顶堆
        if (left.size() > right.size() + 1) {
            right.offer(left.poll());
        }
        // 情况2:小顶堆比大顶堆多,需要把小顶堆的堆顶移到大顶堆
        else if (right.size() > left.size()) {
            left.offer(right.poll());
        }
    }

    public double findMedian() {
        // 奇数个元素,大顶堆的堆顶就是中位数
        if (left.size() > right.size()) {
            return left.peek();
        }
        // 偶数个元素,取两个堆顶的平均值
        else {
            return (left.peek() + right.peek()) / 2.0;
        }
    }
}

二刷复盘要点

  • 两个堆的作用是什么?大顶堆维护左半部分,小顶堆维护右半部分,这样两个堆顶就是整个数据的中间两个数,取中位数的时间复杂度是 O (1)。
  • 易错点:堆的大小调整逻辑一定要写对,尤其是大顶堆和小顶堆的大小关系,否则会导致中位数计算错误。
  • 面试常问:除了双堆法,还有没有其他方法?(如二分查找维护有序数组,但插入时间复杂度是 O (n),效率不如双堆法)

三、两道题的核心模板总结

表格

题目 核心数据结构 关键技巧 时间复杂度
前 K 个高频元素 HashMap + 小顶堆 维护堆大小不超过 k,效率最优 O(n log k)
数据流的中位数 大顶堆 + 小顶堆 双堆平衡,堆顶直接取中位数 addNum: O(log n), findMedian: O(1)
相关推荐
Chase_______1 小时前
LeetCode 2090 题解:半径为 k 的子数组平均值,定长滑动窗口经典题一文搞懂
算法·leetcode·职场和发展
MicroTech20251 小时前
微算法科技(NASDAQ :MLGO)量子图像加权平均滤波:以量子优势重构图像处理效率与精度
科技·算法·重构
jieyucx1 小时前
Go 语言函数入门:定义、参数、返回值
c++·算法·golang·入门·函数
Codector1 小时前
在Ubuntu中使用Edge侧边栏无法添加和查看同步的侧边栏问题解决
笔记·ubuntu·develop
Brilliantwxx1 小时前
【C++】认识标准库STL(1)
开发语言·c++·笔记·程序人生·算法
凯尔萨厮1 小时前
创建Springboot空项目
java·spring boot
想成为优秀工程师的爸爸1 小时前
第二十四篇技术笔记:郭大侠学DoIP - 从“偶睡破庙”到“天字一号”
网络·笔记·网络协议·tcp/ip·信息与通信
天才少女爱迪生1 小时前
【迪士尼机器人】硬件接入记录(自用笔记版)
笔记
啦啦啦_99991 小时前
2. 梯度下降算法分类 & 梯度下降与正规方程对比
人工智能·算法·分类