
拿到这道题,我第一反应就是:这不就是上一题第 K 大元素的升级版吗?
思考过程
1. 题目拆解
题目要求:给一个整数数组 nums 和一个整数 k,返回数组中出现频率前 k 高 的元素。比如示例 1:nums = [1,1,1,2,2,3],k=2,1 出现 3 次,2 出现 2 次,3 出现 1 次,所以前 2 高的就是 [1,2]。
2. 我的核心思路
上一题我们是直接对数组元素求第 K 大,这一题的区别在于:我们要比较的不是元素本身,而是元素的出现频率。所以我的思路瞬间就通了:
这一题就是比上一题多记个频率,然后拿频率做堆,统计前 k 个,再去 map 拿数!
拆解成 3 步:
- 第一步:统计频率:用哈希表(HashMap)遍历数组,统计每个数字出现的次数。
- 第二步:维护小顶堆 :用一个大小为
k的小顶堆,按照「频率」来排序,只保留频率最高的 k 个元素。 - 第三步:提取结果:遍历堆,把里面的元素拿出来,就是我们要的前 k 个高频元素。
3. 为什么还是用小顶堆?
和上一题的逻辑完全一致:
- 小顶堆的堆顶永远是当前堆里频率最低的元素。
- 我们维护一个大小为
k的小顶堆,遍历所有频率:- 元素入堆,一旦堆大小超过
k,就把堆顶(频率最低的)删掉。
- 元素入堆,一旦堆大小超过
- 遍历结束后,堆里剩下的就是频率最高的 k 个元素,完美符合需求。
- 时间复杂度
O(n logk),比全量排序O(n logn)高效很多,空间复杂度O(n)(哈希表存储频率)。
完整代码实现
java
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// 1. 第一步:用HashMap统计每个数字的出现频率
Map<Integer, Integer> freqMap = new HashMap<>();
for (int num : nums) {
freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
}
// 2. 第二步:创建小顶堆,按照「频率」升序排序(堆顶是频率最小的)
// 小顶堆里存的是数组元素,比较规则是它们对应的频率
PriorityQueue<Integer> minHeap = new PriorityQueue<>((a, b) -> freqMap.get(a) - freqMap.get(b));
// 3. 遍历哈希表的所有key,维护堆的大小为k
for (int num : freqMap.keySet()) {
minHeap.offer(num);
// 堆大小超过k,就把堆顶(频率最低的)删掉
if (minHeap.size() > k) {
minHeap.poll();
}
}
// 4. 第三步:把堆里的元素拿出来,就是前k个高频元素
int[] res = new int[k];
for (int i = 0; i < k; i++) {
res[i] = minHeap.poll();
}
return res;
}
}
代码逐行拆解
1. 统计频率:HashMap 的使用
java
Map<Integer, Integer> freqMap = new HashMap<>();
for (int num : nums) {
freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
}
- 我们用
key存数组中的数字,value存它出现的次数。 getOrDefault(num, 0)是一个非常实用的写法:如果数字第一次出现,默认次数是 0,然后 + 1;如果已经存在,就拿到当前次数再 + 1,完美避免空指针。
2. 创建小顶堆:自定义比较器
java
PriorityQueue<Integer> minHeap = new PriorityQueue<>((a, b) -> freqMap.get(a) - freqMap.get(b));
- 这里是核心!和上一题直接用默认小顶堆不同,我们需要自定义比较规则 :
- 比较的不是
a和b本身,而是它们在freqMap里的频率。 (a, b) -> freqMap.get(a) - freqMap.get(b)表示:频率小的排在前面,也就是小顶堆,堆顶永远是当前堆里频率最低的元素。
- 比较的不是
3. 维护堆的大小
java
for (int num : freqMap.keySet()) {
minHeap.offer(num);
if (minHeap.size() > k) {
minHeap.poll();
}
}
- 遍历哈希表的所有数字,依次入堆。
- 一旦堆的大小超过
k,就把堆顶(频率最低的)删掉,保证堆里永远只存频率最高的 k 个元素。
4. 提取结果
java
int[] res = new int[k];
for (int i = 0; i < k; i++) {
res[i] = minHeap.poll();
}
- 遍历堆,把里面的元素依次弹出,存入结果数组。
- 因为是小顶堆,弹出的顺序是「频率从低到高」,但题目要求可以按任意顺序返回,所以完全没问题。
用示例走一遍流程
我们用示例 1:nums = [1,1,1,2,2,3],k=2 来走一遍完整流程。
1. 统计频率
freqMap 最终结果:{1:3, 2:2, 3:1}
2. 维护小顶堆
- 入堆 1 → 堆:
[1](大小 1 ≤2,不删) - 入堆 2 → 堆:
[2,1](频率 2 < 3,堆顶是 2,大小 2 ≤2,不删) - 入堆 3 → 堆:
[3,1,2](频率 1 <2 < 3,堆顶是 3,大小 3>2,删堆顶 3)→ 堆:[2,1]
3. 提取结果
堆里剩下 [2,1],依次弹出:2、1,结果数组 [2,1](题目允许任意顺序,和示例的[1,2]等价)✅
我的做题心得
这道题本质上就是 第 K 大元素的变形题,核心逻辑完全一致,只是把比较元素大小换成了比较元素频率」。
我的核心思考就是一句话:
这一题就是比上一题多记个频率,然后拿频率做堆,统计前 k 个,再去 map 拿数!
只要你吃透了上一题的小顶堆思路,这道题就是手到擒来。这类 TopK 问题的通用解法就是:
- 统计你需要比较的指标(频率、大小等)
- 用大小为 k 的小顶堆维护前 k 大的元素
- 提取结果