cpp
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> mp; // key: 数字, value: 出现频率
// 默认是大顶堆,存储 pair<频率, 数字>
// pair 比较时先看第一个元素(频率),再看第二个元素(数字)
priority_queue<pair<int, int>> pq;
vector<int> res;
// 1. 统计频率:遍历一遍数组 O(N)
for(int num : nums) {
mp[num]++;
}
// 2. 将频率和数字压入大顶堆:O(M log M),M 是去重后的数字个数
for(auto it = mp.begin(); it != mp.end(); it++) {
// 注意:这里把 it->second(频率)放在前面,方便堆按频率排序
pq.push(pair<int, int>(it->second, it->first));
}
// 3. 弹出前 k 个频率最高的数字:O(K log M)
while(k--) {
res.push_back(pq.top().second); // 取出数字部分
pq.pop();
}
return res;
}
};
2. 运行步骤模拟
假设输入:nums = [1, 1, 1, 2, 2, 3], k = 2
第一步:哈希表计数
mp = {1: 3, 2: 2, 3: 1}(数字 1 出现了 3 次,依此类推)
第二步:压入大顶堆 堆会根据 pair 的第一个元素(频率)自动调整,此时堆的内容(逻辑结构)如下:
-
(3, 1)<- 堆顶(频率最高) -
(2, 2) -
(1, 3)
第三步:取出结果
-
第一次
pop:取回1(频率为 3),res = [1]。 -
第二次
pop:取回2(频率为 2),res = [1, 2]。 -
k减到 0,循环结束。
最终结果 :[1, 2]。
在 C++ STL 中,priority_queue(优先队列)是一个非常实用的容器适配器。
简单来说,它就像是一个**"自带排队规则"的队列**。普通的队列(queue)是先进先出(FIFO),而优先队列是**"最优先的先出"**。
1. 核心特性:谁强谁先出
在 priority_queue 中,元素并不是按照进入的先后顺序排列的,而是按照优先级排列的。
-
默认行为(大顶堆):默认情况下,优先级最高的是"数值最大"的元素。即使你是最后一个进去的,只要你最大,你也会排在最前面。
-
底层实现 :它的底层通常是用 二叉堆 (Binary Heap) 实现的。这保证了插入元素和删除堆顶元素的时间复杂度都是 O(\\log N)。
2. 基本操作
它的接口和普通的 stack 或 queue 非常相似:
| 操作 | 函数 | 说明 | 时间复杂度 |
|---|---|---|---|
| 入队 | pq.push(val) |
将元素插入队列并自动调整位置 | O(\\log N) |
| 出队 | pq.pop() |
删除优先级最高的元素(堆顶) | O(\\log N) |
| 查顶 | pq.top() |
返回优先级最高的元素(不删除) | O(1) |
| 判空 | pq.empty() |
检查队列是否为空 | O(1) |
3. 三种常见的定义方式
面试中经常需要根据需求改变排序规则:
A. 默认:大顶堆(大的先出)
C++
// 默认把最大的整数放在堆顶
priority_queue<int> pq;
B. 小顶堆(小的先出)
如果你想找"前 K 个最小"或者让小的数字先出来:
C++
// 需要三个参数:元素类型,底层容器,比较函数
priority_queue<int, vector<int>, greater<int>> pq;
C. 自定义比较(处理结构体或 Pair)
就像你之前写的 pair<频率, 数字>:
C++
// 存储 pair,默认先比较 pair.first,再比较 pair.second
priority_queue<pair<int, int>> pq;
4. 形象理解:急诊室排队
-
普通队列 (
queue):银行办业务。谁先来谁先办,即便你只是存 100 块,也要等前面取 100 万的人办完。 -
优先队列 (
priority_queue):医院急诊室。医生不看谁先来,而是看谁的伤势最重(优先级最高)。伤势最重的人(比如大顶堆里的最大值)永远第一个就诊。
5. 为什么面试爱考它?
因为它在处理 "最" 字问题时极其高效:
-
找数组中最大的 K 个数。
-
合并 K 个有序链表(每次取 K 个节点里最小的)。
-
迪杰斯特拉(Dijkstra)最短路径算法(每次取距离最短的点)。
既然你已经知道 priority_queue 是基于"堆"实现的,你想看看它是如何通过"上浮"和"下沉"操作来维持这个顺序的吗?(这能帮你更好地理解 O(\\log N) 是怎么来的)