堆-前 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. 提取结果
相关推荐
renhongxia12 小时前
世界模型,是“空中楼阁”还是AGI的“最后一块拼图”?
运维·服务器·数据库·人工智能·算法·agi
G.O.G.O.G3 小时前
LeetCode SQL 从入门到精通(MySQL)06(上)
数据库·sql·mysql·leetcode
zephyr054 小时前
动态规划-最长上升子序列问题
算法·动态规划
闪电悠米5 小时前
力扣hot100-56.合并区间-排序详解
数据结构·算法·leetcode·贪心算法·排序算法
卡提西亚6 小时前
leetcode-1438. 绝对差不超过限制的最长连续子数组
算法·leetcode·职场和发展
Java面试题总结6 小时前
LeetCode 93.复原IP地址
算法·leetcode·职场和发展·.net
从零开始的代码生活_7 小时前
C++ 多态详解:虚函数、动态绑定、抽象类与虚表原理
开发语言·c++·后端·学习·算法
yuannl107 小时前
图的存储方式
数据结构
Tisfy7 小时前
LeetCode 3867.数对的最大公约数之和:按题目说的做(gcd)
算法·leetcode·题解·模拟·最大公约数·gcd
泷寂7 小时前
最小生成树 (MST基础)
算法