题目描述
给定一个整数数组 nums
和一个整数 k
,请返回其中出现频率前 k
高的元素。例如,输入 nums = [1,1,1,2,2,3], k = 2
,输出 [1,2]
。
核心难点分析
1. 哈希表的作用
为什么使用哈希表?
哈希表(HashMap
)用于统计每个元素出现的频率。遍历数组时,我们需要快速记录每个元素的出现次数,而哈希表的插入和查询操作平均时间复杂度为 O(1),非常适合这种场景。
2. 优先级队列的选择
为什么使用小顶堆而不是大顶堆?
优先级队列(堆)用于维护当前频率最高的 k
个元素。
- 如果使用大顶堆,堆顶是最大频率元素。当堆大小超过
k
时,需要弹出堆顶元素,这会导致丢失当前最大的元素,最终无法保留前k
个高频元素。 - 小顶堆的堆顶是最小频率元素。当新元素的频率大于堆顶时,替换堆顶元素,这样可以确保堆中始终保留频率前
k
大的元素。此操作的时间复杂度为 O(log k),效率更高。
解题思路分步解析
步骤 1:统计元素频率
遍历数组,使用哈希表记录每个元素的出现次数。
Map<Integer, Integer> cnt = new HashMap<>();
for (int n : nums) {
cnt.put(n, cnt.getOrDefault(n, 0) + 1);
}
步骤 2:维护小顶堆
- 创建小顶堆,按频率升序排列。
- 遍历哈希表,保持堆的大小不超过
k
。当堆满时,若新元素频率更高,则替换堆顶。
PriorityQueue<int[]> res = new PriorityQueue<>((p1, p2) -> p1[1] - p2[1]);
for (Map.Entry<Integer, Integer> entry : cnt.entrySet()) {
if (res.size() < k) {
res.add(new int[] { entry.getKey(), entry.getValue() });
} else {
if (entry.getValue() > res.peek()[1]) {
res.poll();
res.add(new int[] { entry.getKey(), entry.getValue() });
}
}
}
步骤 3:提取结果
从小顶堆中依次取出元素,逆序填充结果数组。
int[] ans = new int[k];
for (int i = 0; i < k; i++) {
ans[i] = res.poll()[0];
}
return ans;
关键知识点详解
哈希表的优势
- 快速统计频率:哈希表通过键值对存储元素及其频率,查找和插入操作高效。
- 去重处理:自动合并重复元素的计数。
小顶堆的工作机制
- 堆的排序规则:通过比较器
(p1, p2) -> p1[1] - p2[1]
实现升序排列,堆顶元素是当前堆中的最小频率。 - 动态维护前 k 大元素:当堆满时,只有比堆顶频率更高的元素才能加入,确保堆中元素始终是当前最大的
k
个。
时间复杂度分析
- 统计频率:O(n),n 为数组长度。
- 堆操作:每次插入/删除操作 O(log k),最坏情况下执行 n 次,总复杂度 O(n log k)。
- 总复杂度:O(n log k),优于大顶堆的 O(n log n)。
为什么不用大顶堆?
假设使用大顶堆,我们需要将所有元素插入堆中,然后弹出前 k
个元素。此时堆的大小为 n
,插入操作的时间复杂度为 O(n log n)。当 n
远大于 k
时,这种方法的效率明显低于小顶堆的 O(n log k)。
完整代码
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> cnt = new HashMap<>();
for (int n : nums) {
cnt.put(n, cnt.getOrDefault(n, 0) + 1);
}
PriorityQueue<int[]> res = new PriorityQueue<>((p1, p2) -> p1[1] - p2[1]);
for (Map.Entry<Integer, Integer> entry : cnt.entrySet()) {
if (res.size() < k) {
res.add(new int[] { entry.getKey(), entry.getValue() });
} else {
if (entry.getValue() > res.peek()[1]) {
res.poll();
res.add(new int[] { entry.getKey(), entry.getValue() });
}
}
}
int[] ans = new int[k];
for (int i = 0; i < k; i++) {
ans[i] = res.poll()[0];
}
return ans;
}
}
总结
- 哈希表用于高效统计元素频率。
- 小顶堆以 O(n log k) 的时间复杂度动态维护前
k
个高频元素。 - 掌握这两种数据结构的结合使用,是解决此类 Top K 问题的关键。