这题和前面的第 K 个最大元素有点像,都是在做“前 K 名”的维护。 只不过上一题比的是数值大小,这一题比的是出现频率。
题目要求返回数组中出现频率前 k 高的元素,顺序不限。
所以这题的关键不在排序数组,而在于:
先统计频率,再维护前
k个高频元素。
题目链接
题目描述
给你一个整数数组 nums 和一个整数 k,请你返回其中出现频率前 k 高的元素。
你可以按任意顺序返回答案。

解法:Counter + 小顶堆
这题最经典、也最顺手的写法,就是:
- 先用
Counter统计每个元素出现的次数 - 再用一个大小不超过
k的小顶堆,维护当前前k个高频元素
代码实现
import heapqfrom collections import Counter
class Solution(object): def topKFrequent(self, nums, k): """ :type nums: List[int] :type k: int :rtype: List[int] """ counter = Counter(nums) heap = []
for item, freq in counter.items(): heapq.heappush(heap, (freq, item)) if len(heap) > k: heapq.heappop(heap)
return [item for freq, item in heap]第一步:先统计频率
这一步很直白:
counter = Counter(nums)比如:
nums = [1, 1, 1, 2, 2, 3]统计结果就是:
{1: 3, 2: 2, 3: 1}意思是:
1出现了 3 次2出现了 2 次3出现了 1 次
既然题目比较的是“谁更高频”,那就必须先把这个频率表做出来。
第二步:为什么用小顶堆
堆里存的不是单独的元素,而是:
(频率, 元素值)比如:
(3, 1)(2, 2)(1, 3)因为 Python 的 heapq 默认是小顶堆,所以堆顶永远是当前堆里频率最小的那个。
这正好适合这题。 因为我们想维护的是:
当前频率最高的前
k个元素
所以每来一个新元素:
- 先把它压入堆
- 如果堆的大小超过了
k - 就把堆顶弹出去
被弹出去的,就是当前前 k 名里最不够格的那个。
为什么这样做一定对
因为整个过程中,堆始终只保留“目前见过的前 k 高频元素”。
- 如果一个新元素频率更高,它就会把低频元素挤掉
- 如果一个新元素频率不够高,它即使先进堆,也会很快被弹出去
等遍历结束后,堆里剩下的自然就是出现频率最高的前 k 个元素。
结合样例走一遍
比如:
nums = [1, 1, 1, 2, 2, 3]k = 2先统计频率:
counter = { 1: 3, 2: 2, 3: 1}然后依次入堆。
放入 (3, 1)
heap = [(3, 1)]放入 (2, 2)
heap = [(2, 2), (3, 1)]放入 (1, 3)
heap = [(1, 3), (3, 1), (2, 2)]这时堆大小已经超过 k = 2,所以弹出堆顶:
heapq.heappop(heap)被弹出的就是:
(1, 3)剩下:
[(2, 2), (3, 1)]最后提取元素值,得到:
[2, 1]因为题目允许任意顺序返回,所以 [1, 2] 和 [2, 1] 都是正确答案。
复杂度分析
假设数组长度为 n,不同元素个数为 m。
时间复杂度
Counter(nums)统计频率:O(n)- 遍历
m个不同元素,维护小顶堆:每次堆操作是O(log k)
所以总时间复杂度是:
O(n + m log k)很多时候也会简写成:
O(n log k)空间复杂度
Counter需要O(m)- 堆最多存
k个元素,需要O(k)
总空间复杂度可以记为:
O(m)这题和 215 的区别
这题和 LeetCode 215《数组中的第 K 个最大元素》很像,都是用堆维护“前 K 名”。
但两题维护的东西不一样:
215 题维护的是
元素值本身目标是:
维护最大的
k个数
347 题维护的是
(频率, 元素)目标是:
维护频率最高的
k个元素
一句话概括就是:
215 比大小,347 比人气。
一个看战斗力,一个看出场率。 都用堆,但赛道不同。
补充:为什么不用直接排序频率表
当然也可以先统计频率,再把所有元素按频率排序。
比如:
counter = Counter(nums)sorted_items = sorted(counter.items(), key=lambda x: x[1], reverse=True)return [item for item, freq in sorted_items[:k]]这样也能做出来。
但它的时间复杂度通常是:
O(m log m)相比之下,小顶堆只需要维护 k 个元素,效率会更好,尤其是当 k 远小于不同元素个数 m 时,更有优势。
小结
这题真正的主线很清楚:
- 先统计频率
- 再用小顶堆维护前
k个高频元素 - 堆满了就弹出频率最小的那个
如果只记一句话,那我建议记这个:
先数人头,再留前 K 名。
这题不难,重点就是把“统计”和“维护前 K 名”这两步拆开想清楚。 想通了之后,代码就会写得很顺。🦐