堆-前 K 个高频元素

拿到这道题,我第一反应就是:这不就是上一题第 K 大元素的升级版吗?

思考过程

1. 题目拆解

题目要求:给一个整数数组 nums 和一个整数 k,返回数组中出现频率前 k 高 的元素。比如示例 1:nums = [1,1,1,2,2,3]k=2,1 出现 3 次,2 出现 2 次,3 出现 1 次,所以前 2 高的就是 [1,2]

2. 我的核心思路

上一题我们是直接对数组元素求第 K 大,这一题的区别在于:我们要比较的不是元素本身,而是元素的出现频率。所以我的思路瞬间就通了:

这一题就是比上一题多记个频率,然后拿频率做堆,统计前 k 个,再去 map 拿数!

拆解成 3 步:

  1. 第一步:统计频率:用哈希表(HashMap)遍历数组,统计每个数字出现的次数。
  2. 第二步:维护小顶堆 :用一个大小为 k 的小顶堆,按照「频率」来排序,只保留频率最高的 k 个元素。
  3. 第三步:提取结果:遍历堆,把里面的元素拿出来,就是我们要的前 k 个高频元素。

3. 为什么还是用小顶堆?

和上一题的逻辑完全一致:

  • 小顶堆的堆顶永远是当前堆里频率最低的元素。
  • 我们维护一个大小为 k 的小顶堆,遍历所有频率:
    • 元素入堆,一旦堆大小超过 k,就把堆顶(频率最低的)删掉。
  • 遍历结束后,堆里剩下的就是频率最高的 k 个元素,完美符合需求。
  • 时间复杂度 O(n logk),比全量排序 O(n logn) 高效很多,空间复杂度 O(n)(哈希表存储频率)。

完整代码实现

java 复制代码
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        // 1. 第一步:用HashMap统计每个数字的出现频率
        Map<Integer, Integer> freqMap = new HashMap<>();
        for (int num : nums) {
            freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
        }

        // 2. 第二步:创建小顶堆,按照「频率」升序排序(堆顶是频率最小的)
        // 小顶堆里存的是数组元素,比较规则是它们对应的频率
        PriorityQueue<Integer> minHeap = new PriorityQueue<>((a, b) -> freqMap.get(a) - freqMap.get(b));

        // 3. 遍历哈希表的所有key,维护堆的大小为k
        for (int num : freqMap.keySet()) {
            minHeap.offer(num);
            // 堆大小超过k,就把堆顶(频率最低的)删掉
            if (minHeap.size() > k) {
                minHeap.poll();
            }
        }

        // 4. 第三步:把堆里的元素拿出来,就是前k个高频元素
        int[] res = new int[k];
        for (int i = 0; i < k; i++) {
            res[i] = minHeap.poll();
        }
        return res;
    }
}

代码逐行拆解

1. 统计频率:HashMap 的使用

java 复制代码
Map<Integer, Integer> freqMap = new HashMap<>();
for (int num : nums) {
    freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
}
  • 我们用 key 存数组中的数字,value 存它出现的次数。
  • getOrDefault(num, 0) 是一个非常实用的写法:如果数字第一次出现,默认次数是 0,然后 + 1;如果已经存在,就拿到当前次数再 + 1,完美避免空指针。

2. 创建小顶堆:自定义比较器

java 复制代码
PriorityQueue<Integer> minHeap = new PriorityQueue<>((a, b) -> freqMap.get(a) - freqMap.get(b));
  • 这里是核心!和上一题直接用默认小顶堆不同,我们需要自定义比较规则
    • 比较的不是 ab 本身,而是它们在 freqMap 里的频率。
    • (a, b) -> freqMap.get(a) - freqMap.get(b) 表示:频率小的排在前面,也就是小顶堆,堆顶永远是当前堆里频率最低的元素。

3. 维护堆的大小

java 复制代码
for (int num : freqMap.keySet()) {
    minHeap.offer(num);
    if (minHeap.size() > k) {
        minHeap.poll();
    }
}
  • 遍历哈希表的所有数字,依次入堆。
  • 一旦堆的大小超过 k,就把堆顶(频率最低的)删掉,保证堆里永远只存频率最高的 k 个元素

4. 提取结果

java 复制代码
int[] res = new int[k];
for (int i = 0; i < k; i++) {
    res[i] = minHeap.poll();
}
  • 遍历堆,把里面的元素依次弹出,存入结果数组。
  • 因为是小顶堆,弹出的顺序是「频率从低到高」,但题目要求可以按任意顺序返回,所以完全没问题。

用示例走一遍流程

我们用示例 1:nums = [1,1,1,2,2,3]k=2 来走一遍完整流程。

1. 统计频率

freqMap 最终结果:{1:3, 2:2, 3:1}

2. 维护小顶堆

  • 入堆 1 → 堆:[1](大小 1 ≤2,不删)
  • 入堆 2 → 堆:[2,1](频率 2 < 3,堆顶是 2,大小 2 ≤2,不删)
  • 入堆 3 → 堆:[3,1,2](频率 1 <2 < 3,堆顶是 3,大小 3>2,删堆顶 3)→ 堆:[2,1]

3. 提取结果

堆里剩下 [2,1],依次弹出:21,结果数组 [2,1](题目允许任意顺序,和示例的[1,2]等价)✅

我的做题心得

这道题本质上就是 第 K 大元素的变形题,核心逻辑完全一致,只是把比较元素大小换成了比较元素频率」。

我的核心思考就是一句话:

这一题就是比上一题多记个频率,然后拿频率做堆,统计前 k 个,再去 map 拿数!

只要你吃透了上一题的小顶堆思路,这道题就是手到擒来。这类 TopK 问题的通用解法就是:

  1. 统计你需要比较的指标(频率、大小等)
  2. 用大小为 k 的小顶堆维护前 k 大的元素
  3. 提取结果
相关推荐
啊哦呃咦唔鱼2 小时前
LeetCodehot100-23合并 K 个升序链表
算法
kobesdu2 小时前
laser_line_extraction线段提取开源功能包解读和使用例程
人工智能·算法·机器人·ros
abant22 小时前
leetcode 105 前序中序构建二叉树
算法·leetcode·职场和发展
6Hzlia2 小时前
【Hot 100 刷题计划】 LeetCode 438. 找到字符串中所有字母异位词 | C++ 滑动窗口题解
c++·算法·leetcode
Mem0rin2 小时前
[Java/数据结构]线性表之栈与队列
java·开发语言·数据结构
生信研究猿2 小时前
leetcode 234.回文链表
python·leetcode·链表
Mr_Xuhhh2 小时前
深入理解Java数组:从定义到高阶应用
开发语言·python·算法
倦王2 小时前
力扣日刷复习:
算法·leetcode·职场和发展
py有趣2 小时前
力扣热门100题之二叉树的中序遍历
算法·leetcode·职场和发展