蓝桥杯 Java B 组 之堆的基础(优先队列实现 Top K 问题)

news/2025/2/25 2:30:15/

Day 6:堆的基础(优先队列实现 Top K 问题)


📖 一、什么是堆(Heap)?

堆(Heap) 是一种特殊的二叉树结构,满足:

  • 最大堆(Max Heap)父节点 ≥ 子节点
  • 最小堆(Min Heap)父节点 ≤ 子节点

堆通常用于实现优先队列(Priority Queue),它能够快速找到最大/最小的元素,常用于: ✅ Top K 问题(前 K 大/小的元素)
数据流中的中位数
任务调度(如 CPU 任务管理)

Java 的 PriorityQueue 默认是 最小堆,可以用 自定义 Comparator 实现最大堆


📖 二、练习 1:求数据流中的中位数

🔹 1. 题目描述

假设你有一个不断流入数字的数据流,需要在任意时刻 求出所有已到达数字的中位数

  • 中位数:排序后,奇数个元素取中间值,偶数个元素取中间两数的平均值

示例

输入数据流:1, 2, 3, 4, 5
中位数变化:
- 插入 1,当前中位数 = 1
- 插入 2,当前中位数 = (1+2)/2 = 1.5
- 插入 3,当前中位数 = 2
- 插入 4,当前中位数 = (2+3)/2 = 2.5
- 插入 5,当前中位数 = 3

🔹 2. 解题思路

我们可以用 两个堆(Heap) 来维护数据流:

  • 最大堆(leftHeap):存储较小的一半数字
  • 最小堆(rightHeap):存储较大的一半数字

操作规则

  1. 新元素先插入最大堆,然后把最大堆的最大值插入最小堆(保证 leftHeap <= rightHeap)。
  2. 平衡两个堆的大小(最大堆的元素个数最多只能比最小堆多 1)。
  3. 求中位数
    • 元素总数为奇数,中位数 = 最大堆的堆顶元素
    • 元素总数为偶数,中位数 = (最大堆堆顶 + 最小堆堆顶)/ 2.0

🔹 3. Java 代码

import java.util.Collections;
import java.util.PriorityQueue;public class MedianFinder {private PriorityQueue<Integer> leftHeap;  // 最大堆(存较小的一半数)private PriorityQueue<Integer> rightHeap; // 最小堆(存较大的一半数)public MedianFinder() {leftHeap = new PriorityQueue<>(Collections.reverseOrder()); // 大顶堆rightHeap = new PriorityQueue<>(); // 小顶堆}public void addNum(int num) {leftHeap.offer(num);  // 先加入最大堆rightHeap.offer(leftHeap.poll());  // 把最大堆的最大值转移到最小堆if (leftHeap.size() < rightHeap.size()) { leftHeap.offer(rightHeap.poll());  // 保持最大堆元素个数 >= 最小堆}}public double findMedian() {if (leftHeap.size() > rightHeap.size()) {return leftHeap.peek();  // 奇数个元素,返回最大堆堆顶} else {return (leftHeap.peek() + rightHeap.peek()) / 2.0;  // 偶数个元素,返回两堆堆顶均值}}public static void main(String[] args) {MedianFinder medianFinder = new MedianFinder();int[] nums = {1, 2, 3, 4, 5};for (int num : nums) {medianFinder.addNum(num);System.out.println("当前中位数:" + medianFinder.findMedian());}}
}

✅ 运行结果

当前中位数:1.0
当前中位数:1.5
当前中位数:2.0
当前中位数:2.5
当前中位数:3.0

🔹 4. 时间复杂度分析

  • 插入元素:O(log n)(插入堆的时间复杂度)
  • 查找中位数:O(1)(直接返回堆顶元素)

📖 三、练习 2:前 K 个高频元素

🔹 1. 题目描述

给定一个整数数组 nums 和一个整数 k,返回出现次数最多的 前 k 个元素

示例

输入:nums = [1,1,1,2,2,3], k = 2
输出:[1, 2]

🔹 2. 解题思路

哈希表 + 最小堆

  1. HashMap 统计每个元素的频率
  2. 使用大小为 K 的最小堆 PriorityQueue 存储前 K 个高频元素
  3. 遍历 HashMap,维护堆的大小
    • 如果堆中元素个数 < K,直接入堆
    • 否则,如果当前元素频率比堆顶元素大,弹出堆顶并加入新元素

🔹 3. Java 代码

import java.util.*;public class TopKFrequentElements {public static int[] topKFrequent(int[] nums, int k) {// 统计频率HashMap<Integer, Integer> freqMap = new HashMap<>();for (int num : nums) {freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);}// 最小堆,按频率排序(堆顶存最小频率)PriorityQueue<Map.Entry<Integer, Integer>> minHeap = new PriorityQueue<>(Comparator.comparingInt(Map.Entry::getValue));// 遍历 HashMap,将前 K 个高频元素存入堆for (Map.Entry<Integer, Integer> entry : freqMap.entrySet()) {minHeap.offer(entry);if (minHeap.size() > k) {minHeap.poll();  // 维护堆的大小不超过 K}}// 取出堆中的元素int[] result = new int[k];for (int i = k - 1; i >= 0; i--) {result[i] = minHeap.poll().getKey();}return result;}public static void main(String[] args) {int[] nums = {1,1,1,2,2,3};int k = 2;System.out.println(Arrays.toString(topKFrequent(nums, k)));  // 输出:[1, 2]}
}

✅ 运行结果

[1, 2]

🔹 4. 时间复杂度分析

  • 统计频率:O(n)
  • 维护堆(K 大小):O(n log k)
  • 总时间复杂度:O(n log k)(比直接排序 O(n log n) 更优)

📖 四、总结

问题数据结构时间复杂度
求数据流的中位数最大堆 + 最小堆O(log n) 插入, O(1) 查询
前 K 个高频元素哈希表 + 最小堆O(n log k)

📖 五、堆的常考点与易错点

优先队列默认是最小堆,需要 Collections.reverseOrder() 变成最大堆
Top K 问题需要维护固定大小的最小堆
求数据流中位数,需要两个堆相互平衡



http://www.ppmy.cn/news/1574744.html

相关文章

智联招聘爬虫

使用Python和Selenium进行招聘信息爬取 在当今数字化时代&#xff0c;数据已成为企业决策的重要依据。对于人力资源部门或求职者而言&#xff0c;获取最新的招聘信息至关重要。然而&#xff0c;手动浏览和收集招聘信息不仅耗时费力&#xff0c;而且效率低下。为了解决这个问题&…

Keeppalived 实现Nginx 的高可用集群

一、实验介绍 1、 本实验将通过 Keepalived 来实现 Nginx 的 HA 集群&#xff0c;当集群中的主服务器发生故障后&#xff0c;业务自动切换到备用主机上。 2、实验组网介绍&#xff1a;本实验由三台虚拟机&#xff08;这里131.19为MASTER 131.20为BACKUP&#xff09;组成&…

C#中级教程(2)——走进 C# 面向对象编程:从基础到进阶的深度探索

一、为什么选择面向对象编程 在软件开发的演进过程中&#xff0c;随着程序规模和复杂度的不断增加&#xff0c;传统的编程方式逐渐暴露出局限性。面向对象编程应运而生&#xff0c;它就像是一位智慧的组织者&#xff0c;将程序中的功能进行模块化划分。每个模块各司其职&#x…

前端防重复请求终极方案:从Loading地狱到精准拦截的架构升级

&#x1f525; 事故现场还原&#xff1a;疯狂点击引发的血案 凌晨1点23分&#xff0c;监控系统突然告警&#xff1a; &#x1f4c9; 服务器CPU飙升至98% &#x1f5c3;️ 数据库出现3000脏数据 &#x1f4a5; 用户端弹出上百个错误弹窗 事故原因&#xff1a;黑产脚本通过0.5秒…

【第四节】C++设计模式(创建型模式)-Builder(建造者)模式

目录 引言 一、Builder 模式概述 二、Builder 模式举例 三、Builder 模式的结构 四、Builder 模式的实现 五、Builder 模式的优缺点 六、总结 引言 Builder 模式是一种创建型设计模式&#xff0c;旨在将复杂对象的构建过程与其表示分离。通过一步步构建对象&#xff0c;…

Unity3D Addressable资源管理实战详解

引言 在Unity3D开发中&#xff0c;资源管理是一个非常重要的环节。随着项目规模的增大&#xff0c;资源的管理和加载变得越来越复杂。Unity3D提供了Addressable Asset System&#xff08;可寻址资源系统&#xff09;来帮助开发者更高效地管理资源。本文将详细介绍Addressable资…

微博的定位与IP属地:两者有何异同?

在微博这一社交媒体平台上&#xff0c;用户不仅能分享生活点滴、交流思想观点&#xff0c;还能通过一系列功能定位自己的社交圈子和信息获取渠道。其中&#xff0c;“微博定位”与“IP属地”是两个常被提及的概念&#xff0c;那么&#xff0c;微博的定位和IP属地一样吗&#xf…

Java如何解决彻底解决,大数据量excel导出内存溢出问题

一、核心工具选型&#xff1a;流式处理框架 1. 使用EasyExcel&#xff08;推荐&#xff09; 阿里巴巴开源的EasyExcel基于流式读写设计&#xff0c;通过逐行处理数据避免内存堆积。 优势&#xff1a; 内存占用低&#xff0c;支持百万级数据导出&#xff1b; 内置分页写入、自…