题目来源:LeetCode347.前 K 个高频元素
问题抽象: 给定一个整数数组 nums
和一个整数 k
,要求返回数组中 出现频率最高的前 k
个元素,满足以下核心需求:
-
频率统计与排序规则:
- 需精确统计数组中 每个元素的出现次数;
- 按频率 从高到低排序,选取频率排名前
k
的元素(频率相同的元素可任意排序)。
-
计算约束:
- 时间复杂度 必须优于 O(n log n)(
n
为数组长度),即禁止全排序; - 空间复杂度无严格限制,但需避免冗余存储。
- 时间复杂度 必须优于 O(n log n)(
-
边界处理:
- 输入数组非空,
k
满足1 ≤ k ≤ 不同元素的数量
; - 当多个元素频率相同时,输出需包含所有频率不低于第
k
名的元素; - 特殊值:
k = 1
时返回最高频元素,k = 数组元素种类数
时返回所有元素(任意顺序)。
- 输入数组非空,
输入:整数数组 nums
,整数 k
(1 ≤ k ≤ 不同元素数量
)。
输出:前 k
个高频元素组成的数组(元素顺序不限)。
解题思路
要找出数组中出现频率前 k
高的元素,可以分三步实现:
- 频率统计:遍历数组,使用哈希表记录每个元素出现的频率。
- 最小堆维护:使用最小堆(优先队列)维护频率最高的
k
个元素。堆中元素按频率升序排序(堆顶频率最小),遍历哈希表时:- 当堆大小
< k
时,直接加入元素 - 当堆大小
= k
时,若当前元素频率 > 堆顶频率,则弹出堆顶后加入新元素
- 当堆大小
- 结果输出:将堆中元素输出为数组(顺序无关,题目不要求有序)
为何用最小堆?
最小堆只需维护 k
个元素,时间复杂度 O(n log k)
。最大堆需存所有元素,时间复杂度 O(n log n)
,效率更低
代码实现(Java版)🔥点击下载源码
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// 1. 统计频率:O(n)
Map<Integer, Integer> freqMap = new HashMap<>();
for (int num : nums) {
freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
}
// 2. 最小堆维护TopK:O(n log k)
PriorityQueue<int[]> minHeap = new PriorityQueue<>((a, b) -> a[1] - b[1]);
for (Map.Entry<Integer, Integer> entry : freqMap.entrySet()) {
int num = entry.getKey();
int freq = entry.getValue();
// 堆未满时直接加入
if (minHeap.size() < k) {
minHeap.offer(new int[]{num, freq});
}
// 堆已满时,仅当当前频率>堆顶频率才替换
else if (freq > minHeap.peek()[1]) {
minHeap.poll(); // 弹出堆顶(最小频率)
minHeap.offer(new int[]{num, freq});
}
}
// 3. 输出结果:O(k)
int[] result = new int[k];
for (int i = 0; i < k; i++) {
result[i] = minHeap.poll()[0];
}
return result;
}
}
代码说明
-
频率统计(
freqMap
):- 使用
HashMap
存储元素及其出现次数 - 遍历数组
nums
,通过getOrDefault
快速更新频率 - 时间复杂度:
O(n)
,空间复杂度:O(n)
- 使用
-
最小堆维护(
minHeap
):- 优先队列按频率升序排序(
a[1] - b[1]
) - 遍历哈希表时动态维护堆:
- 堆大小
< k
→ 直接加入元素 - 堆大小
= k
→ 仅当新元素频率 > 堆顶频率时替换
- 堆大小
- 时间复杂度:
O(n log k)
(最坏情况n
次堆操作)
- 优先队列按频率升序排序(
-
结果输出:
- 从堆中依次取出元素(无需考虑顺序)
- 时间复杂度:
O(k)
(k
次堆弹出操作)
总时间复杂度:O(n log k)
,优于排序解法(O(n log n)
) 。总空间复杂度:O(n)
(哈希表占主导)