堆 / 优先队列专题二刷笔记:前 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)
相关推荐
NE_STOP1 小时前
Docker--Docker Swarm集群
java
两年半的个人练习生^_^1 小时前
JMM 进阶:彻底理解 CAS 实现原理
java·开发语言
wuminyu1 小时前
Java锁机制之park和unpark源码剖析
java·linux·c语言·jvm·c++
华山沦贱1 小时前
open62541 V1.5.4版对C++ Builder支持的bug
笔记
梦梦代码精1 小时前
为什么这个开源的AI平台会火?有点东西。。。
人工智能·算法·机器学习·docker·开源
随意起个昵称2 小时前
线性dp-综合刷题1(Not Alone)
算法·动态规划
W_LuYi1852 小时前
手撸极简zkEVM验证器:RISC-V电路实践
java·risc-v
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题 第102题】【并发篇】第2题:volatile 能否保证线程安全?
java·安全·面试
KobeSacre2 小时前
JUC 概述
java·开发语言
稷下元歌2 小时前
七天学会plc 加机器视觉完整笔记:S7-1200 数据类型、存储区与寻址方式(I/Q/M/DB 详解)。
网络·数据库·笔记