1.1 涵盖知识点
-
完全二叉树定义,
-
满足 堆序型的完全二叉树 形成 :堆;
-
使用 优先级队列容器适配器 实现堆 ;
-
优先级队列:优先级队列中元素的出队顺序 与元素的优先级有关。
-
优先级队列中, 三个参数的意义:
template <typename T,
typename Container=std::vector<T>,
typename Compare=std::less<T> >
class priority_queue{
//......
}
1. typename T:指定存储元素的具体类型;
2. typename Container:指定 priority_queue 底层使用的基础容器,
3. typename Compare:指定容器中评定元素优先级所遵循的排序规则,
默认使用std::less<T>按照元素值从大到小进行排序,
还可以使用std::greater<T>按照元素值从小到大排序,
但更多情况下是使用自定义的排序规则。
上述的 数据结构, 在 C++ STL 标准模版库中, 使用了 priority_queue: 优先级队列容器适配器实现了;
-
pair<int, int>: 关联式容器 中的 pair 类模板: 用来将2个普通元素 first, second 创建成一个新的元素,称为键值对 <key, value> ;
-
unordered_map: 关联式容器, 其中存储的元素, 都是一个一个的 “键值对” (<key, value>);
并且 unordered_map 底层实现是一个哈希表,key 无序,且不可重复, key 不可以修改; -
容器中元素的遍历, 这里是 unordered_map 容器, 采用 增强型的 for 循环方式;
-
auto: 自动类型推导;
-
decltype: 声明类型, declare type;
-
向量容器中的 emplace_back() 方法: 在序列尾部生成一个元素;
-
priority_queue 优先级队列 容器适配器: 中的 emplace() 方法: 此方法的作用, 根据既定的排序规则,在容器适配器适当的位置直接 生成该 新元素;
(emplace(Args&&… args),而对于 类对象来说, 可能需要多个数据构造出一个对象, 所以使用 Agrs … args 表示构造一个存储类型的元素,所需要的数据)
2 逻辑步骤
构建一个比较函数, 返回bool 类型; 用于比较两对<key, value> 中, 谁的 value 大, 即对应元素出现的次数更多;
构建 题目任务的 topK 函数, 返回一个向量容器, 其中存储的元素 是前 K 个 高频出现的 元素值;
-
新建一个 无序的关联式容器 unordered_map<> uMap, 用于存放,数组元素以及 对应元素出现的次数;
-
遍历给定的数组, 将给定数组中的元素作为key 存储在 uMap 中, 该元素出现的次数作为 value 存储在 uMap 中;
-
使用优先级队列,构建一个小根堆; 注意priority_que 传入的 三个参数的意义, 使用该优先级队列 按照第三个传入的参数 规则, 形成一个小顶堆;
-
遍历 uMap 中 <key, value>, 将其中元素存入到小根堆中,并作维护大小为 K 的 小顶堆;
4.1 如果堆的大小 == K 个 键值对:
并且此时如果, 小根堆的堆顶中的键值对,<key, value>, value 小于当前 uMap 中的 freq, 则将顶层的 <key, value> 移除,将当前的uMap 中的 <ele, freq> 加入到队列中;
4.2 否则, 堆的大小小于 K 时, 则直接将当前uMap 中的<key, value> 加入堆中; -
创建一个结果集 向量容器 ret; 用于存储小根堆中 每一对<key, value> 中的 key;
-
遍历小根堆, 当小根堆不为空时, 将小根堆中每一对 <key, value> 中的 key , 存入到 ret 中, 并移除当前堆顶的 <key, value>.
-
返回 ret;
#include "unordered_map" // 用于保存数组的元素, 以及元素出现的次数; 其中包含了 pair<key, value> 的模版
#include "vector" // 用于保存最终top k 个元素;
#include "queue" // 调用 priority_queue 构建 小根堆;
using namespace std;
class Solution{
public:
// 制定优先级队列中, 优先级的规则定义; 即判断两个元素 对应的频率更大;
static bool cmp_fun(pair<int,int>& m, pair<int, int>& n ) {
return m.second > n.second; // 按照 value 的大小作为 优先级 标注;
}
vector<int> topKFreq(vector<int>& nums, int k){
// 1. 新建一个 unordered_map 用于存放元素和次数;
unordered_map<int, int> uMap;
// 2. 遍历数组, 将其中的元素, 以及对应次数存入到 uMap 中;
for(auto ele: nums) uMap[ele]++; // <key, value>: key 代表 数组中的元素, value: 代表该元素出现的次数;
// 3. 使用优先级队列, 构造小根堆; 注意优先级队列三个参数的意义;
priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(&cmp_fun) > heap(cmp_fun);
// 4. 遍历uMap 将其中的 <key, value> , 形成一个 大小为K 的小根堆;;
for(auto& [ele, freq]: uMap){
if(heap.size() == k){ // 如果小根堆的大小为 K, 则此时开始 维护 小根堆;
if(heap.top().second < freq){ // 如果堆顶的键值对中的 freq 小于当前的 freq, 则移除堆顶的 键值对, 存入当前的键值对;
heap.pop();
heap.emplace(ele, freq); // 使用 emplace() 优先级队列中的方法;
}
}else{ heap.emplace( ele, freq);} // 否则,堆的大小 小于 K, 直接存入键值对;
}
// 5. 创建一个结果集, 用于保存小根堆中 的 key 数值;
vector<int> result;
// 6. 当小根堆不为空时, 将其中每一对的 key 存入到结果集中;
while (!heap.empty()){
result.push_back(heap.top().first);
heap.pop();
}
return result;
}
};
// 时间复杂度:O(nlogk)
// 空间复杂度:O(n)
class Solution {
public:
// 小顶堆
class mycomparison {
public:
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
// 要统计元素出现频率
unordered_map<int, int> map; // map<nums[i],对应出现的次数>
for (int i = 0; i < nums.size(); i++) {
map[nums[i]]++;
}
// 对频率排序
// 定义一个小顶堆,大小为k
priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
// 用固定大小为k的小顶堆,扫面所有频率的数值
for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
pri_que.push(*it);
if (pri_que.size() > k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
pri_que.pop();
}
}
// 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
vector<int> result(k);
for (int i = k - 1; i >= 0; i--) {
result[i] = pri_que.top().first;
pri_que.pop();
}
return result;
}
};