LeetCode 第 347 题「前 K 个高频元素」
字数 1465 2025-10-26 07:53:21
我来给你讲解 LeetCode 第 347 题「前 K 个高频元素」。
1. 题目描述
给你一个整数数组 nums 和一个整数 k,请你返回其中出现频率前 k 高的元素的集合。可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
要求:时间复杂度必须优于 O(n log n),其中 n 是数组大小。
2. 问题分析
我们要找出出现次数最多的 k 个元素。
一个直观的思路是:
- 统计每个元素出现的次数。
- 根据次数排序,取前 k 个。
但排序的时间复杂度是 O(n log n),虽然可以满足一般情况,但题目要求优于 O(n log n),所以我们需要更优的解法。
3. 思路与步骤
3.1 统计频率
使用哈希表(字典)来统计每个数字出现的次数。
这一步时间复杂度 O(n),空间复杂度 O(n)。
例如 nums = [1,1,1,2,2,3],得到哈希表:
{1: 3, 2: 2, 3: 1}
3.2 如何取前 k 个高频元素
如果对所有元素按频率排序,是 O(m log m),m 是不同数字的个数,最坏 m = n,则 O(n log n),不符合要求。
我们可以用 最小堆(Min-Heap) 来优化:
- 维护一个大小为 k 的最小堆(按频率排序)。
- 遍历哈希表的每个元素:
- 如果堆大小小于 k,直接加入堆。
- 否则,比较当前元素的频率与堆顶(堆中最小频率):
- 如果当前频率大于堆顶频率,弹出堆顶,插入当前元素。
- 否则跳过。
- 最后堆中剩下的就是频率最大的 k 个元素。
为什么用最小堆?
因为堆顶是堆中最小的频率,我们只保留比它大的,这样堆里始终是当前看到的最大的 k 个频率。
3.3 复杂度分析
- 建堆(大小为 k)的插入和删除是 O(log k)。
- 最坏情况下,m 个不同元素都要进堆一次,复杂度 O(m log k)。
- 由于 k ≤ m ≤ n,所以 O(m log k) 优于 O(n log n)。
4. 详细步骤示例
以 nums = [1,1,1,2,2,3], k = 2 为例:
-
统计频率:
1: 3 2: 2 3: 1 -
用最小堆(存储
(频率, 元素),按频率排序):- 插入 (3, 1),堆:[(3, 1)],大小=1 < 2,继续。
- 插入 (2, 2),堆:[(2, 2), (3, 1)] 调整后堆顶是 (2, 2)?不对,最小堆堆顶是最小频率,所以应该是 (2, 2) 在堆顶?我们检查一下:
实际上插入过程:
插入 (3,1) → 堆 [(3,1)]
插入 (2,2) → 堆调整为 [(2,2), (3,1)],堆顶是 (2,2)。 - 插入 (1, 3):
当前堆顶频率=2,新元素频率=1 < 2,不加入(因为我们要保留大的,堆大小已到 k,小的不要)。
-
最后堆中元素:[(2,2), (3,1)],对应的数字是 [2, 1](顺序任意)。
输出 [1, 2]。
5. 实现细节(Python示例)
import heapq
from collections import Counter
def topKFrequent(nums, k):
# 统计频率
count = Counter(nums)
# 建立最小堆
heap = []
for num, freq in count.items():
if len(heap) < k:
heapq.heappush(heap, (freq, num))
else:
if freq > heap[0][0]:
heapq.heapreplace(heap, (freq, num))
# 提取堆中的元素
return [num for freq, num in heap]
6. 其他解法(桶排序)
另一种 O(n) 解法是桶排序(Bucket Sort):
- 创建长度为 n+1 的数组
buckets,buckets[i]存储出现频率为 i 的所有数字。 - 从高频率向低频率遍历,收集 k 个元素。
这种方法在 n 很大但不同数字很少时也高效。
7. 总结
- 核心:哈希表统计 + 最小堆维护 Top K。
- 时间复杂度:O(n log k),优于 O(n log n)。
- 空间复杂度:O(n) 用于哈希表与堆。
这样就能高效解决「前 K 个高频元素」问题。