一、堆的定义
- 堆的定义:堆是一种特别的二叉树,具有两个特性
- 完全二叉树;
- 每一个节点的值都必须 大于等于或者小于等于 其孩子节点的值。
- 堆的特性
- 插入元素:O(logN)
- 删除元素:O(logN)
- 获取 堆 中的最大值或最小值:O(1)
- 堆的分类
- 最大堆:堆中每一个节点的值 都大于等于 其孩子节点的值。所以最大堆的特性是 堆顶元素(根节点)是堆中的最大值。
- 最小堆:堆中每一个节点的值 都小于等于 其孩子节点的值。所以最小堆的特性是 堆顶元素(根节点)是堆中的最小值。
二、堆的实现
C++ 中堆采用优先级队列实现,常用操作如下
priority_queue<int> q;
priority_queue<int, vector<int>, greater<int> > q; //升序队列,小顶堆
priority_queue<int, vector<int>, less<int> > q; //降序队列,大顶堆
q.top();
q.empty();
q.size();
q.push(x);
q.emplace(x) 原地构造一个元素并插入队列
q.pop();
//自定义排序算法
struct cmp{
bool operator()(const pair<string, int>& l, const pair<string, int>& r){
if (l.second == r.second)
return l.first < r.first;
else
return l.second > r.second;
}
};
priority_queue<pair<string, int>, vector<pair<string, int>>, cmp> heap;
三、堆的应用
堆的常见应用有三类:
1、堆排序
2、找前K大的元素
Top K 大元素
解法1:
1. 创建一个「最大堆」;
2. 将所有元素都加到「最大堆」中;
3. 通过边删除边遍历方法,将堆顶元素删除,并将它保存到结果集 T 中;
4. 重复3步骤 K 次,直到取出前K个最大的元素;
解法2:
1.创建一个大小为 K 的「最小堆」;
2.依次将元素添加到「最小堆」中;
3.当「最小堆」的元素个数达到 K 时,将当前元素与堆顶元素进行对比:
4.如果当前元素小于堆顶元素,则放弃当前元素,继续进行下一个元素;
5.如果当前元素大于堆顶元素,则删除堆顶元素,将当前元素加入到「最小堆」中。
6.重复步骤 2 和步骤 3,直到所有元素遍历完毕。
Top K 小元素:与topK大元素解法一致,最大堆换成最小堆即可
3、第K大元素问题
The Kth 大元素
解法1:
1.创建一个「最大堆」;
2.将所有元素都加到「最大堆」中;
3.通过 「边删除边遍历」方法,将堆顶元素删除;
4.重复 3 步骤 K 次,直到获取到第 K 个最大的元素;
解法2:
1.创建一个大小为 K 的「最小堆」;
2.依次将元素添加到「最小堆」中;
3.当「最小堆」的元素个数达到 K 时,将当前元素与堆顶元素进行对比:
如果当前元素小于堆顶元素,则放弃当前元素,继续进行下一个元素;
如果当前元素大于堆顶元素,则删除堆顶元素,将当前元素加入到「最小堆」中。
4.重复步骤 2 和步骤 3,直到所有元素遍历完毕。
The Kth 小元素 :topK大元素解法一致,最大堆换成最小堆即可
四、堆系列问题
堆系列问题题目:https://leetcode-cn.com/tag/heap/problemset/
1、找前K大元素问题
-
剑指 Offer 40. 最小的k个数(easy):典型前 K 小元素问题。创建大小为K的最大堆存前K小的元素,如果小于堆顶则堆顶出堆将新元素入堆,堆内元素即为前K小元素
-
347. 前 K 个高频元素(medium):给出一个整数数组,求出现频率前k高的元素。采用hash记录频次,采用大小为k的存较大元素的最小堆来存放元素,堆内即为较大的k个元素
-
373. 查找和最小的K对数字(medium):给出一堆整数对,求和最小的k对数字。计算完后存到hash中,采用大小为k的存较小元素的最大堆来存放和,堆内即为和最小的k对数字
-
692. 前K个高频单词(medium):统计出现频率最高的前K个元素,并按字母序排列。采用hash存频次,用大小为k的存较大元素的最小堆来存放频次,堆内即为和最大的k个单词,出堆根据字母序排序
-
451. 根据字符出现频率排序(medium):要求根据字符出现频率降序排序。采用hash存频次,用最大堆来存放频次,入堆完再根据出堆顺序排序
-
973. 最接近原点的 K 个点(medium):给出一些坐标求最接近原点的K个点。采用大小为K的存较小元素的最大堆来存距离,堆内即为最近的k个点
-
857. 雇佣 K 名工人的最低成本(hard):给出工人的工作质量及期望薪资,求雇佣k个工人最少需要多少钱。计算工人的性价比,并放入大小为k的存性价比较高的小顶堆中,堆中即为性价比较高的k个工人
2、第K大元素问题
- 703. 数据流中的第 K 大元素(easy):求数据流中第K大的元素。一个大小为K的最小堆存较大的K个数,大于堆顶则入堆,堆顶即为第K大的元素
- 215. 数组中的第K个最大元素(medium):在未排序数组中找第K大的元素。采用K大的最小堆存最大的K个数
- 378. 有序矩阵中第 K 小的元素(medium):求在二维矩阵中找第K小的数。同样是找第K小的数,只是变为了二维数组,用一个大小为K存较小元素的大顶堆,小于堆顶的元素可以入堆,遍历完后堆顶即为结果
- 264. 丑数 II(medium):丑数为只由2/3/5乘的数,求返回第n个丑数。回第n小的丑数,第n小的整数一定在相乘n次内,则求出相乘n次得到的所有丑数,放入大小为n的存较小元素的最大堆中,堆顶即为第n小的丑数
- 313. 超级丑数(medium):超级丑数是指所有质因数都是质数列表中的正整数,求第n个超级丑数。此题与上题的区别为相乘的数变为给定的,解法一致,仍未求出相乘n次得到的超级丑数并放入大小为n存较小元素的最大堆中,堆顶即为第n小的超级丑数
3、找中位数问题
- 295. 数据流的中位数(hard):不断求数据流中的中位数。看到中位数要意识到中位数只是特殊的第K大的元素,仍为找第K大元素问题,中位数问题采用两个堆,一个大小为n/2的小顶堆存较大元素,一个大小为n/2的大顶堆存较小元素,则堆顶即为中间的数
4、堆排序-动态顺序的问题
-
1046. 最后一块石头的重量(easy):给出一堆石头,选出两块石头会粉碎成两块石头的差值。贪心的思想:每次都从最大的石头开始粉碎,且数组会改变。为了实现对改变的数组每次都取到最大值则采用一个最大堆来存
-
23. 合并K个升序链表(hard):给出K个链表数组,对所有链表进行合并排序。采用堆排序实现,所有节点入堆,按照出队顺序构造新链表
-
239. 滑动窗口最大值(hard):给出整数数组,上面有长度为k的滑动窗口,不断求滑动窗口中的最大值。采用最大堆来不断加入元素,当堆顶元素不在滑动窗口中时出堆,否则取堆顶
-
1845. 座位预约管理系统(medium):要求设计一个购票系统,每次取最小的号,支持动态放回。本题要支持动态变化的数组取最小值,动态排序问题采用堆实现,采用小顶堆存数值,每次取堆顶即为最小
-
1753. 移除石子的最大得分(medium):给出三堆石子,每次要选两堆拿走一个石子,当有两堆为空时结束,求最多的得分。贪心的思想:每次都从最高的两组里面取,采用大顶堆来实现动态排序,每次取堆顶两个,处理后存回