347. 前 K 个高频元素(Top K Frequent Elements)

难度:中等 | 主题:堆、哈希表、桶排序

题目

给你一个整数数组 nums 和一个整数 k,返回其中出现频率前 k 高的元素。顺序任意。

示例

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

思路

先讲个故事:班级里的热门话题

老师在班里做了一个话题投票,每个同学可以在纸条上写自己感兴趣的话题编号。现在老师想知道——全班最受欢迎的 K 个话题是哪些?

首先,你需要把每张纸条归类统计(数一数每个编号出现多少次)。然后,从这些统计结果中找出票数最高的 K 个。

前 K 高频 = 先数数(哈希表统计)→ 再排序(堆/桶筛选)。

引导式推导:三层递进


graph LR

    subgraph 三层递进

        A["哈希计数 + 排序<br/>O(n log n)"]

        B["哈希计数 + 小顶堆<br/>O(n log k)"]

        C["桶排序<br/>O(n)"]

    end

    A -->|"k << n 时<br/>全排序浪费"| B

    B -->|"追求理论最优"| C

第 1 层:哈希计数 + 排序

Counter(nums) 统计频率,然后按频率降序排序取前 k 个。

第 2 层:哈希计数 + 小顶堆(推荐)

统计频率后,维护大小为 k 的小顶堆,按频率比较。遍历频率表,当堆未满或当前频率 > 堆顶频率时入堆。

 
nums = [1,1,1,2,2,3], k = 2
 
① Counter → {1:3, 2:2, 3:1}
 
② 小顶堆操作:
 
  (3, 1) 入堆 → [(3,1)]
 
  (2, 2) 入堆 → [(2,2), (3,1)](堆大小=k ✓)
 
  (1, 3) 频率 1 < 堆顶频率 2 → 跳过
 
③ 结果: [1, 2] ✓
 

第 3 层:桶排序(理论最优)

统计频率后,利用”频率上限不超过 n”这个特点,用数组 bucket[freq] 存对应频率的元素。从高到低遍历桶取 k 个。

 
Counter → {1:3, 2:2, 3:1}
 
buckets[3] = [1]
 
buckets[2] = [2]
 
buckets[1] = [3]
 
从高到低取: bucket[3] → [1], bucket[2] → [2] ✓
 

代码

 
import heapq
 
from collections import Counter
 
# 方法1:最小堆(推荐)
 
def topKFrequent(self, nums, k):
 
    count = Counter(nums)
 
    min_heap = []
 
    for num, freq in count.items():
 
        heapq.heappush(min_heap, (freq, num))
 
        if len(min_heap) > k:
 
            heapq.heappop(min_heap)
 
    return [num for freq, num in min_heap]
 
# 方法2:桶排序
 
def topKFrequent(self, nums, k):
 
    count = Counter(nums)
 
    n = len(nums)
 
    freq_buckets = [[] for _ in range(n + 1)]
 
    for num, freq in count.items():
 
        freq_buckets[freq].append(num)
 
    result = []
 
    for freq in range(n, 0, -1):
 
        for num in freq_buckets[freq]:
 
            result.append(num)
 
            if len(result) == k:
 
                return result
 
    return result
 

复杂度

指标堆解法桶排序
时间O(n log k)O(n)
空间O(n)O(n)

实战考量

频率分析

出现在:Top K 模式题,常见。“哈希表统计 + 堆筛选”是大数据场景的经典组合。约 40% 常会从这道题延伸讨论”如果数据大到内存放不下怎么办”。

延伸思考

Q:如果 n 很大,内存放不下 Counter 怎么办?

A:数据流场景用 Reservoir Sampling(蓄水池抽样),或外排序。

Q:要求返回的 k 个元素按频率降序排列呢?

A:最小堆结果需要再排 O(k log k);桶排序天然从高往低取。

Q:时间复杂度的瓶颈在哪里?

A:Counter O(n) 是线性,堆 O(n log k)。如果 k << n,堆很快。

Q:Counter 的时间复杂度?

A:O(n),遍历一次数组统计频率。

Q:桶排序利用了哪个特性?

A:频率上限不超过 n(数组中每个元素最多出现 n 次),所以桶的大小是 n+1,可控。

易错点

  • heapq 是最小堆,存频率最小的在堆顶,超过 k 就弹出

  • 不要写成最大堆的逻辑(存负频率)

  • Counter 是 Python 内置神器,用它很 Pythonic

  • 多个元素频率相同时,返回顺序任意


生活类比

前 K 高频 → 先统计每个话题被说了多少次,再找出最热门的 K 个

就像你在论坛上找热门帖子——先看每个帖子的点赞数,然后找一个能记录 K 个最高点赞的”小本子”,新帖子比本子上最低的高就替换。

桶排序更激进:直接准备 n 个篮子(0 赞 ~ n 赞),把帖子扔进对应篮子里,从高赞篮子往下拿。


相关题目

题目关系
215数组中的第K个最大元素纯堆选第 K 大
295数据流的中位数双堆设计

→ 返回题单:LeetCode学习路线图 > 六、堆

速记卡(面试闪卡)

Q1:一句话讲清「347. 前 K 个高频元素(Top K Frequent Elements)」到底是什么?

A:返回数组中出现频率最高的前 k 个元素,顺序任意(Top K,前 K 大)。

Q2:思路 —— 怎么理解?

A:像论坛找热门帖:先数每个话题票数(哈希),再筛出最高 K 个(Hash Map,哈希表)。

Q3:代码 —— 怎么理解?

A:小顶堆存 (freq,num) 维持 k 个;或桶排序按频率入篮(Min-Heap,最小堆)。

Q4:复杂度 —— 怎么理解?

A:堆解法 O(n log k)、桶排序 O(n),空间均 O(n)(Bucket Sort,桶排序)。

Q5:实战考量 —— 怎么理解?

A:Top K 经典,大数据可蓄水池抽样;k<<n 时堆很快(Frequency Counting,频率统计)。

Q6:核心速记主线有哪些?

  • 先哈希计数频率,再筛前 k

  • 小顶堆 O(n log k) 推荐

  • 桶排序 O(n) 利用频率上限≤n

  • 多个元素同频时返回顺序任意

口诀

A:前 K 高频怎么捞

哈希计数先记牢

小顶堆里留 k 个

桶排更快一遍扫

相关链接