347. 前 K 个高频元素

中等 含交互动画

学完这道你应该能: 说清它为什么用「桶排序」,而不是死记步骤; 合上页面,自己默写出核心代码; 用 30 秒把思路讲清楚。

AI 私教 · 就着本题动画讲
卡住了不用硬扛,按 8 步把这题真正走通。
从读题、试解、找法到手写代码,边问边打勾,适合第一次系统学算法的同学。

题目描述

给数组 nums 和整数 k,返回出现频率最高的 k 个元素。

nums = [1, 1, 1, 2, 2, 3]
k = 2
输出 = [1, 2]

思路解析

最直接的想法:用哈希表数出每个数的次数,再把所有数按次数从大到小排序,取前 k 个。能对,但我们只要前 k 个,却把每个数都排得明明白白,多花了 log n 的排序,浪费。

转折:一个数最多出现 n 次,所以「次数」的取值只有 0~n 这有限几种。开一排桶,桶下标 = 出现次数,把每个数丢进「它次数对应的桶」。再从下标最大(次数最高)的桶往回扫,凑够 k 个就停——这样避开了对全部元素的整体排序,整体只 O(n)。

数次数 · 看第 1 个 1:第一步先扫一遍数组数次数。看到第一个 1,哈希表里 cnt[1] 记为 1。

数次数 · 第 2、3 个 1:又遇到两个 1,cnt[1] 累加到 3。计数就是「见一个加一个」,哈希表查改都是 O(1)。

数次数 · 看 2:遇到第一个 2,cnt 里新增 cnt[2]=1。

数次数 · 第 2 个 2:又一个 2,cnt[2] 累加到 2

数次数 · 看 3 · 计数完成:最后一个 3,cnt[3]=1。数组扫完,得到每个数的次数:1→3、2→2、3→1。这就是装桶的原料。

装桶 · 桶下标 = 出现次数:第二步装桶:遍历 cnt,把每个数丢进「它次数对应的桶」。1 进「次数 3」桶、2 进「次数 2」桶、3 进「次数 1」桶。注意桶是按次数分的,次数再大也超不过 n,所以桶的数量有限——这正是能用桶排序的根。

取 Top K · 先扫「次数 3」桶:第三步从下标最大(次数最高)的桶往回扫。先看「次数 3」桶,里面是 1,收走,res=[1],已有 1 个,还差 1 个凑够 k=2。

取 Top K · 再扫「次数 2」桶 · 凑够:往回到「次数 2」桶,取出 2,res=[1,2],凑够 k=2 个,立刻停。「次数 1」桶根本没碰——这就是比整体排序省的地方:低频的数压根不用管。答案 [1, 2]

Top K 高频的通杀思路:先哈希计数,再拿「次数」当桶下标。只要某个量(次数、分数、年龄)取值范围有限,桶排序就能避开 O(n log n) 的整体排序。

参考代码(Python)

Python
1def topKFrequent(nums, k):
2    from collections import Counter
3    cnt = Counter(nums)                          # 数次数: 13, 22, 31
4    buckets = [[] for _ in range(len(nums) + 1)] # 桶下标 0~n
5    for x, c in cnt.items():
6        buckets[c].append(x)                    # 按次数 c 装桶
7    res = []
8    for c in range(len(buckets) - 1, 0, -1):    # 从高频桶往回扫
9        for x in buckets[c]:
10            res.append(x)
11            if len(res) == k: return res        # 凑够 k 个就停

复杂度分析

  • 时间复杂度:O(n) —— 计数 O(n) + 装桶 O(不同元素数) + 倒扫桶 O(n),都是线性,没有 log n 的排序
  • 空间复杂度:O(n) —— 哈希表存至多 n 个不同的数 + 长为 n+1 的桶数组

套路模板

工程里常用堆解法:维护大小为 k 的小顶堆,频率比堆顶大就替换,堆里始终只留 k 个,O(n log k)。一行 nlargest 就是这个意思,省内存且不依赖「次数有界」。

Python
1import heapq
2from collections import Counter
3cnt = Counter(nums)
4# 维护一个大小为 k 的小顶堆,堆顶永远是当前 k 个里频率最小的
5return heapq.nlargest(k, cnt.keys(), key=cnt.get)  # O(n log k)

易错点

  • 错误写法:直接对所有数按频率 sorted 再切前 k正确写法:用桶(次数当下标)或大小为 k 的小顶堆(只要前 k 个却把全部 n 个排了序,白搭 O(n log n);桶排序 O(n)、堆 O(n log k) 都更省)
  • 错误写法:桶数组开 len(nums) 这么长正确写法:开 len(nums) + 1,因为次数最大可达 n(一个数最多出现 n 次,要能放下下标 n,桶长必须是 n+1,否则 buckets[n] 越界)
  • 错误写法:从下标 0 的桶开始顺着扫正确写法:从最大下标往 1 倒着扫(要的是「高频」,必须先取次数大的桶;顺扫会先拿到低频元素,答案全错)

以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握

下一题 →23. 合并 K 个升序链表 ← 返回题库