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

二、题解
方法一:哈希表 + 小顶堆
思路:使用 HashMap 统计每个元素出现的次数,再使用 PriorityQueue 小顶堆维护出现频率最高的 k 个元素。
1. 核心思路
由于题目要求返回出现频率前 k 高的元素,所以我们需要先统计每个数字出现的次数。
核心思想是:
- 第一点:用
HashMap统计每个数字出现的次数; - 第二点:用小顶堆保存当前频率最高的
k个数字; - 第三点:当堆中元素超过
k个时,弹出频率最小的元素。
这样遍历结束后,堆中剩下的就是前 k 个高频元素。
2. 具体步骤
- 使用
HashMap<Integer, Integer>统计每个数字出现的次数。 - 创建一个小顶堆,堆中存放数字,排序规则按照数字出现频率从小到大排序。
- 遍历哈希表中的每个数字,将数字加入小顶堆。
- 如果堆的大小超过
k,就弹出堆顶元素,也就是当前频率最小的数字。 - 最后将堆中剩下的元素放入结果数组并返回。
3. 关键逻辑
PriorityQueue<Integer> heap = new PriorityQueue<>(
(a, b) -> countMap.get(a) - countMap.get(b)
);
for (int num : countMap.keySet()) {
heap.offer(num);
if (heap.size() > k) {
heap.poll();
}
}
解释:
- 小顶堆按照出现频率从小到大排序;
- 堆顶永远是当前堆中频率最低的元素;
- 如果堆大小超过
k,说明当前保留的元素太多了; - 此时弹出频率最低的元素,就可以保证堆中始终保留频率最高的
k个元素。
4. 代码
import java.util.*;
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// 1. 统计每个数字出现的次数
Map<Integer, Integer> countMap = new HashMap<>();
for (int num : nums) {
countMap.put(num, countMap.getOrDefault(num, 0) + 1);
}
// 2. 定义小顶堆
// 堆中存放数字,按照数字出现频率从小到大排序
PriorityQueue<Integer> heap = new PriorityQueue<>(
(a, b) -> countMap.get(a) - countMap.get(b)
);
// 3. 遍历每个不同的数字
for (int num : countMap.keySet()) {
heap.offer(num);
// 如果堆中元素超过 k 个,就移除频率最小的元素
if (heap.size() > k) {
heap.poll();
}
}
// 4. 返回结果
int[] result = new int[k];
for (int i = 0; i < k; i++) {
result[i] = heap.poll();
}
return result;
}
}
5. 复杂度分析
时间复杂度:
说明:
n是数组nums的长度;m是数组中不同元素的个数;- 统计频率需要遍历数组一次,时间复杂度是 ;
- 每个不同元素最多入堆一次,堆的大小最多为
k,所以堆操作时间复杂度是 。
空间复杂度:
说明:
HashMap需要存储每个不同元素的出现次数,空间复杂度是 ;- 小顶堆最多存储
k个元素,空间复杂度是 。
方法二:哈希表 + 桶排序
思路:先用 HashMap 统计每个数字出现的次数,再根据出现次数把数字放入对应的桶中,最后从高频桶开始收集答案。
1. 核心思路
方法一使用小顶堆维护前 k 个高频元素,而方法二利用了一个特点:
一个数字在数组中最多出现 nums.length 次。
因此可以创建一个桶数组:
List<Integer>[] buckets = new List[nums.length + 1];
其中:
buckets[i]
表示出现次数为 i 的所有数字。
这种方法的核心是:
- 先统计每个数字出现的次数;
- 再按照出现次数把数字放入对应桶中;
- 最后从频率最高的桶开始往前遍历,收集前
k个数字。
2. 具体步骤
- 使用
HashMap<Integer, Integer>统计每个数字出现的次数。 - 创建桶数组
buckets,数组下标表示出现次数。 - 遍历哈希表,将数字放入对应频率的桶中。
- 从
buckets.length - 1开始往前遍历,也就是从最高频率开始遍历。 - 依次收集桶中的数字,直到收集到
k个元素为止。 - 返回结果数组。
3. 关键逻辑
for (int i = buckets.length - 1; i >= 0; i--) {
if (buckets[i] == null) {
continue;
}
for (int num : buckets[i]) {
result[index] = num;
index++;
if (index == k) {
return result;
}
}
}
解释:
- 从后往前遍历桶数组,表示从高频率到低频率遍历;
- 如果当前桶为空,说明没有数字出现了这个次数,直接跳过;
- 如果当前桶不为空,就依次取出里面的数字;
- 当收集到
k个元素时,直接返回结果。
4. 代码
import java.util.*;
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// 1. 统计每个数字出现的次数
Map<Integer, Integer> countMap = new HashMap<>();
for (int num : nums) {
countMap.put(num, countMap.getOrDefault(num, 0) + 1);
}
// 2. 创建桶数组
// 下标表示出现次数,buckets[i] 表示出现 i 次的所有数字
List<Integer>[] buckets = new List[nums.length + 1];
// 3. 把数字放入对应频率的桶中
for (int num : countMap.keySet()) {
int count = countMap.get(num);
if (buckets[count] == null) {
buckets[count] = new ArrayList<>();
}
buckets[count].add(num);
}
// 4. 从高频率到低频率收集结果
int[] result = new int[k];
int index = 0;
for (int i = buckets.length - 1; i >= 0; i--) {
if (buckets[i] == null) {
continue;
}
for (int num : buckets[i]) {
result[index] = num;
index++;
// 收集到 k 个元素后直接返回
if (index == k) {
return result;
}
}
}
return result;
}
}
5. 复杂度分析
时间复杂度:
说明:
- 统计频率需要遍历数组一次,时间复杂度是 ;
- 将数字放入桶中,最多处理所有不同元素,时间复杂度是 ;
- 从高频桶开始收集结果,最多遍历所有桶和所有元素,时间复杂度是 。
空间复杂度:
说明:
HashMap最多存储n个不同元素;- 桶数组长度是
nums.length + 1; - 所以空间复杂度是 。
评论