题目描述:
给你一个整数数组
nums和一个整数k,请你返回其中出现频率前k高的元素。你可以按 任意顺序 返回答案。示例 1:
**输入:**nums = 1,1,1,2,2,3, k = 2
输出:1,2
示例 2:
**输入:**nums = 1, k = 1
输出:1
示例 3:
**输入:**nums = 1,2,1,2,1,2,3,1,3,2, k = 2
输出:1,2
解题思路:
方法一:哈希表 + 小根堆
核心思路:
-
哈希表统计频率 :
unordered_map<int, int>记录每个元素的出现次数 -
小根堆维护 Top K:遍历哈希表,用小根堆维护频率最高的 k 个元素
为什么用小根堆?
-
堆里维护 k 个元素,堆顶是频率最低的
-
新元素频率 > 堆顶频率 → 弹出堆顶,新元素入堆
-
最终堆里就是频率最高的 k 个
具体过程示例:
nums = [1,1,1,2,2,3], k = 2
第1步:统计频率
频率表: {1:3, 2:2, 3:1}
第2步:小根堆维护 Top K
遍历 1(频率3): 堆 [(3,1)]
遍历 2(频率2): 堆 [(2,2), (3,1)](堆顶频率2)
遍历 3(频率1): 1 < 堆顶2 → 不入堆
最终堆: [(2,2), (3,1)]
取出元素: [2, 1] ✅
代码实现:
cpp
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
// 第1步:统计频率
unordered_map<int, int> freq;
for (int num : nums) {
freq[num]++;
}
// 第2步:小根堆维护 Top K
// pair<频率, 元素>
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
for (auto& [num, count] : freq) {
pq.push({count, num});
if (pq.size() > k) {
pq.pop(); // 弹出频率最小的
}
}
// 第3步:取出结果
vector<int> result;
while (!pq.empty()) {
result.push_back(pq.top().second);
pq.pop();
}
return result;
}
};
复杂度分析:
| 维度 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n log k) | 统计频率 O(n) + 堆操作 O(m log k),m 是不同元素个数 |
| 空间复杂度 | O(m) | 哈希表 O(m) + 堆 O(k) |
n 是数组长度,m 是不同元素个数,k 是要求的个数。
关键细节:
1. 为什么用 pair<int, int>?
priority_queue 默认按 pair 的 first 比较:
-
pair<频率, 元素>:按频率排序 -
greater<pair<int,int>>:小根堆,频率小的在堆顶
2. 为什么用 greater?
-
priority_queue默认是大根堆(频率大的在堆顶) -
我们需要小根堆(频率小的在堆顶),这样才能淘汰频率最小的
3. 结构化绑定(C++17)
cpp
for (auto& [num, count] : freq) {
// num 是键,count 是值
}
等价于:
cpp
for (auto& pair : freq) {
int num = pair.first;
int count = pair.second;
}
方法二:桶排序(O(n))
思路:频率最大为 n,用桶存储每个频率对应的元素。
代码实现:
cpp
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> freq;
for (int num : nums) freq[num]++;
// 桶:下标是频率,值是元素列表
int n = nums.size();
vector<vector<int>> buckets(n + 1);
for (auto& [num, count] : freq) {
buckets[count].push_back(num);
}
// 从高频率往低频率取
vector<int> result;
for (int i = n; i >= 0 && result.size() < k; i--) {
for (int num : buckets[i]) {
result.push_back(num);
if (result.size() == k) break;
}
}
return result;
}
};
复杂度:时间 O(n),空间 O(n)
优点:时间复杂度 O(n),比堆更快
缺点:需要额外 O(n) 空间
方法三:快速选择(O(n) 平均)
思路:把频率数组做快速选择,找第 k 大的频率。
// 类似 215 题,但比较的是频率
复杂度:平均 O(n),最坏 O(n²)
三种方法对比:
| 方法 | 时间复杂度 | 空间复杂度 | 推荐度 |
|---|---|---|---|
| 哈希表 + 小根堆 | O(n log k) | O(m) | ⭐⭐⭐⭐⭐ |
| 哈希表 + 桶排序 | O(n) | O(n) | ⭐⭐⭐⭐ |
| 哈希表 + 快速选择 | 平均 O(n) | O(m) | ⭐⭐⭐⭐ |