【LeetCode 热题 100】347. 前 K 个高频元素——(解法二)最大堆

Problem: 347. 前 K 个高频元素
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

整体思路

这段代码同样旨在解决 “前 K 个高频元素” 问题。此版本采用了一种 “哈希表 + 最大堆” 的组合方法。与使用最小堆维护 Top K 的方法不同,这个实现是将所有唯一元素都放入一个最大堆中,然后从中提取出最大的 k 个。

算法的整体思路可以清晰地分解为以下三个步骤:

  1. 第一步:频率统计

    • 与之前的方法完全相同,算法首先使用一个 哈希表 (HashMap) frequencyMap 来高效地统计数组 nums 中每个数字出现的频率。
    • 这一步为后续的排序提供了基础数据。
  2. 第二步:构建最大堆

    • 这是算法的核心。它创建了一个 最大堆 (Max-Heap)
    • 堆中存储的内容:这个堆直接存储的是数字本身(即 frequencyMap 的键)。
    • 比较逻辑:通过一个自定义的比较器 (a, b) -> frequencyMap.get(b) - frequencyMap.get(a),堆在进行内部排序时,会去 frequencyMap 中查找每个数字对应的频率,并根据频率进行比较。frequencyMap.get(b) - frequencyMap.get(a) 实现了按频率降序排列,从而构成了一个最大堆。
    • 填充堆:通过 maxHeap.addAll(frequencyMap.keySet()),将 frequencyMap 中所有唯一的数字(M 个)一次性地全部加入到最大堆中。Java 的 PriorityQueueaddAll 时会进行“堆化(heapify)”操作,这是一个 O(M) 的高效过程。
  3. 第三步:提取 Top K 结果

    • 此时,最大堆的堆顶就是频率最高的元素,第二个是次高的,以此类推。
    • 算法创建一个大小为 k 的结果数组 ans,然后通过一个 for 循环,连续从最大堆中弹出(poll) k 次元素。
    • 每次弹出的元素都是当前堆中频率最高的,将它们依次存入 ans 数组。
    • 返回 ans 数组。

这个方法在逻辑上等价于先排序再取前k个,但利用了堆这种数据结构。相比于维护大小为k的最小堆的方法,它的时间复杂度略高,因为需要对所有 M 个唯一元素进行建堆操作。

完整代码

class Solution {
    /**
     * 找出数组中出现频率最高的前 k 个元素。
     * @param nums 整数数组
     * @param k 需要找出的元素个数
     * @return 包含前 k 个高频元素的数组
     */
    public int[] topKFrequent(int[] nums, int k) {
        // 步骤 1: 使用 HashMap 统计每个数字的出现频率
        Map<Integer, Integer> frequencyMap = new HashMap<>();
        for (int num : nums) {
            frequencyMap.put(num, frequencyMap.getOrDefault(num, 0) + 1);
        }

        // 步骤 2: 构建一个最大堆
        // 堆中存储的是数字本身。
        // 比较器通过查询 frequencyMap 来按频率进行降序比较,从而实现最大堆。
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> frequencyMap.get(b) - frequencyMap.get(a));

        // 将所有唯一数字(map 的键集合)一次性加入最大堆。
        // 这个操作会触发一个 O(M) 的堆化过程。
        maxHeap.addAll(frequencyMap.keySet());
        
        // 步骤 3: 从最大堆中提取前 k 个元素
        int[] ans = new int[k];
        for (int i = 0; i < k; i++) {
            // 依次从堆中弹出频率最高的元素
            ans[i] = maxHeap.poll();
        }
        return ans;
    }
}

时空复杂度

时间复杂度:O(N + M log M)

  1. 频率统计:遍历 nums 数组一次,时间复杂度为 O(N)
  2. 构建最大堆
    • maxHeap.addAll(frequencyMap.keySet()):将 M 个唯一元素加入优先队列。这个批量添加操作在 Java 中被优化为“堆化(heapify)”,其时间复杂度为 O(M)
    • 备选分析:如果逐个添加 M 个元素,总时间复杂度会是 O(M log M)。但 addAll 通常更高效。
  3. 提取结果
    • 从堆中 pollk 个元素。每次 poll 操作都需要 O(log M) 的时间(因为堆的大小从 M 逐渐减小到 M-k)。
    • 因此,这部分的总时间复杂度是 O(k log M)

综合分析
总时间复杂度 = O(N) (统计) + O(M) (建堆) + O(k log M) (提取)。
由于 k <= M <= N,其中 O(k log M) 这一项在 k 较小时可能不是主导项,但为了覆盖所有情况,我们需要考虑它。
因此,总的时间复杂度可以写为 O(N + k log M)
如果考虑到最坏情况,即 k 接近 M,则复杂度变为 O(N + M log M),这与完全排序的时间复杂度相当。

空间复杂度:O(M)

  1. 主要存储开销
    • HashMap frequencyMap: 需要存储所有 M 个唯一元素及其频率。空间为 O(M)
    • PriorityQueue maxHeap: 需要存储这 M 个唯一数字。空间为 O(M)

综合分析
算法所需的额外空间由哈希表和最大堆共同构成,两者的大小都与唯一元素的数量 M 成正比。因此,总的空间复杂度为 O(M)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xumistore

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值