堆 / 优先队列专题二刷笔记:前 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)
相关推荐
csdn_aspnet5 小时前
C语言 Lomuto分区算法(Lomuto Partition Algorithm)
c语言·开发语言·算法
Dicky-_-zhang5 小时前
消息队列Kafka/RocketMQ选型与高可用架构:从单体到100万TPS的演进
java·jvm
晨曦中的暮雨5 小时前
4.15腾讯 CSIG云服务产线 一面
java·开发语言
fake_ss1985 小时前
AI时代学习全栈项目开发的新范式
java·人工智能·学习·架构·个人开发·学习方法
谙弆悕博士5 小时前
【附C源码】从零实现C语言堆数据结构:原理、实现与应用
c语言·数据结构·算法··数据结构与算法
茉莉玫瑰花茶6 小时前
工作流的常见模式 [ 1 ]
java·服务器·前端
未若君雅裁6 小时前
Spring AOP、日志切面与声明式事务原理
java·后端·spring
Upsy-Daisy6 小时前
AI Agent 项目学习笔记(二):Spring AI 与 ChatClient 主链路解析
人工智能·笔记·学习
No8g攻城狮7 小时前
【人大金仓】wsl2+ubuntu22.04安装人大金仓数据库V9
java·数据库·spring boot·非关系型数据库
xiaoerbuyu12337 小时前
开源Java 邮箱 基于SpringBoot+Vue前后端分离的电子邮件
java·开发语言