347. 前 K 个高频元素详解:哈希表与优先级队列的经典结合

题目描述

给定一个整数数组 nums 和一个整数 k,请返回其中出现频率前 k 高的元素。例如,输入 nums = [1,1,1,2,2,3], k = 2,输出 [1,2]

核心难点分析

1. 哈希表的作用

为什么使用哈希表?
哈希表(HashMap)用于统计每个元素出现的频率。遍历数组时,我们需要快速记录每个元素的出现次数,而哈希表的插入和查询操作平均时间复杂度为 O(1),非常适合这种场景。

2. 优先级队列的选择

为什么使用小顶堆而不是大顶堆?
优先级队列(堆)用于维护当前频率最高的 k 个元素。

  • 如果使用大顶堆,堆顶是最大频率元素。当堆大小超过 k 时,需要弹出堆顶元素,这会导致丢失当前最大的元素,最终无法保留前 k 个高频元素。
  • 小顶堆的堆顶是最小频率元素。当新元素的频率大于堆顶时,替换堆顶元素,这样可以确保堆中始终保留频率前 k 大的元素。此操作的时间复杂度为 O(log k),效率更高。

解题思路分步解析

步骤 1:统计元素频率

遍历数组,使用哈希表记录每个元素的出现次数。

Map<Integer, Integer> cnt = new HashMap<>();
for (int n : nums) {
    cnt.put(n, cnt.getOrDefault(n, 0) + 1);
}

步骤 2:维护小顶堆

  • 创建小顶堆,按频率升序排列。
  • 遍历哈希表,保持堆的大小不超过 k。当堆满时,若新元素频率更高,则替换堆顶。
PriorityQueue<int[]> res = new PriorityQueue<>((p1, p2) -> p1[1] - p2[1]);
for (Map.Entry<Integer, Integer> entry : cnt.entrySet()) {
    if (res.size() < k) {
        res.add(new int[] { entry.getKey(), entry.getValue() });
    } else {
        if (entry.getValue() > res.peek()[1]) {
            res.poll();
            res.add(new int[] { entry.getKey(), entry.getValue() });
        }
    }
}

步骤 3:提取结果

从小顶堆中依次取出元素,逆序填充结果数组。

int[] ans = new int[k];
for (int i = 0; i < k; i++) {
    ans[i] = res.poll()[0];
}
return ans;

关键知识点详解

哈希表的优势

  • 快速统计频率:哈希表通过键值对存储元素及其频率,查找和插入操作高效。
  • 去重处理:自动合并重复元素的计数。

小顶堆的工作机制

  • 堆的排序规则:通过比较器 (p1, p2) -> p1[1] - p2[1] 实现升序排列,堆顶元素是当前堆中的最小频率。
  • 动态维护前 k 大元素:当堆满时,只有比堆顶频率更高的元素才能加入,确保堆中元素始终是当前最大的 k 个。

时间复杂度分析

  • 统计频率:O(n),n 为数组长度。
  • 堆操作:每次插入/删除操作 O(log k),最坏情况下执行 n 次,总复杂度 O(n log k)。
  • 总复杂度:O(n log k),优于大顶堆的 O(n log n)。

为什么不用大顶堆?

假设使用大顶堆,我们需要将所有元素插入堆中,然后弹出前 k 个元素。此时堆的大小为 n,插入操作的时间复杂度为 O(n log n)。当 n 远大于 k 时,这种方法的效率明显低于小顶堆的 O(n log k)。


完整代码

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> cnt = new HashMap<>();
        for (int n : nums) {
            cnt.put(n, cnt.getOrDefault(n, 0) + 1);
        }
        PriorityQueue<int[]> res = new PriorityQueue<>((p1, p2) -> p1[1] - p2[1]);
        for (Map.Entry<Integer, Integer> entry : cnt.entrySet()) {
            if (res.size() < k) {
                res.add(new int[] { entry.getKey(), entry.getValue() });
            } else {
                if (entry.getValue() > res.peek()[1]) {
                    res.poll();
                    res.add(new int[] { entry.getKey(), entry.getValue() });
                }
            }
        }
        int[] ans = new int[k];
        for (int i = 0; i < k; i++) {
            ans[i] = res.poll()[0];
        }
        return ans;
    }
}

总结

  • 哈希表用于高效统计元素频率。
  • 小顶堆以 O(n log k) 的时间复杂度动态维护前 k 个高频元素。
  • 掌握这两种数据结构的结合使用,是解决此类 Top K 问题的关键。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值