
本文深入探讨了如何利用哈希表和桶排序高效地找出数组中出现频率最高的 k 个元素。文章详细解释了构建频率映射、利用桶排序按频率组织元素,并着重阐明了在填充频率桶时,遍历哈希表的键集(keyset)而非原始数组(nums)的重要性,以确保每个频率桶中只包含唯一的元素,从而避免结果错误。
在数据处理和算法设计中,“找出数组中出现频率最高的 K 个元素”是一个经典问题。它常用于推荐系统、热点数据分析等场景。解决此问题有多种方法,其中一种高效且易于理解的方案是结合哈希表进行频率统计,再利用桶排序的思想组织和提取结果。
给定一个整数数组 nums 和一个整数 k,任务是返回数组中出现频率最高的 k 个元素。返回的顺序不重要。
核心思路可以分解为以下两步:
第一步是计算数组中每个元素的出现频率。Java中的 HashMap 是实现这一目标的理想选择。
立即学习“Java免费学习笔记(深入)”;
import java.util.*;
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// 1. 统计每个数字的频率
Map<Integer, Integer> freqMap = new HashMap<>();
for (int num : nums) {
freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
}
// ... 后续步骤
}
}这段代码遍历 nums 数组,对于每个数字,如果它已经在 freqMap 中,则将其频率加 1;否则,将其添加到 freqMap 中,频率设为 1。
统计完频率后,我们需要一种方式来根据频率快速访问元素。这里引入“桶排序”的思想:
创建一个 List<Integer>[] bucket 数组,其大小为 nums.length + 1。bucket 的索引 i 将代表频率 i,而 bucket[i] 中存储的是所有出现频率为 i 的数字列表。
// ... (接上文代码)
// 2. 创建频率桶
// bucket[i] 存储所有频率为 i 的数字列表
List<Integer>[] bucket = new ArrayList[nums.length + 1];
// 遍历频率映射的键集,将数字放入对应的频率桶
for (int num : freqMap.keySet()) { // 关键点:遍历 freqMap.keySet()
int frequency = freqMap.get(num);
if (bucket[frequency] == null) {
bucket[frequency] = new ArrayList<>();
}
bucket[frequency].add(num);
}
// ... 后续步骤这是本解决方案中最容易混淆但至关重要的一点。许多初学者可能会疑惑,为什么不能直接遍历原始 nums 数组来填充桶,就像这样:
// 错误示例:遍历原始 nums 数组填充桶
for (int num : nums) { // 错误!
int frequency = freqMap.get(num);
if (bucket[frequency] == null) {
bucket[frequency] = new ArrayList<>();
}
bucket[frequency].add(num); // 可能会添加重复元素到桶中
}原因在于:
因此,遍历 freqMap.keySet() 是确保每个数字只被放入其频率桶一次,并且每个桶中只包含唯一数字的关键。
构建完频率桶后,我们只需从最高频率的桶开始(即从 bucket 数组的末尾向前遍历),依次收集元素,直到收集到 k 个为止。
// ... (接上文代码)
// 3. 从桶中提取前 K 个高频元素
int[] result = new int[k];
int resultIndex = 0; // 用于填充结果数组的索引
// 从最高频率开始(桶数组末尾)向前遍历
for (int i = bucket.length - 1; i >= 0 && resultIndex < k; i--) {
if (bucket[i] != null) {
for (int element : bucket[i]) {
result[resultIndex++] = element;
if (resultIndex == k) { // 如果已收集到 K 个元素,则提前返回
return result;
}
}
}
}
return result; // 理论上不会执行到这里,因为 k 总是小于等于总的唯一元素数
}
}import java.util.*;
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// 1. 统计每个数字的频率
// Key: 数字本身, Value: 该数字出现的频率
Map<Integer, Integer> freqMap = new HashMap<>();
for (int num : nums) {
freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
}
// 2. 创建频率桶
// bucket[i] 存储所有频率为 i 的数字列表
// 桶数组的大小为 nums.length + 1,因为最大频率不可能超过 nums.length
List<Integer>[] bucket = new ArrayList[nums.length + 1];
// 遍历频率映射的键集 (keySet),将每个唯一的数字放入其对应的频率桶中
// 这样做保证了每个桶中存储的数字都是唯一的
for (int num : freqMap.keySet()) {
int frequency = freqMap.get(num);
if (bucket[frequency] == null) {
bucket[frequency] = new ArrayList<>();
}
bucket[frequency].add(num);
}
// 3. 从桶中提取前 K 个高频元素
int[] result = new int[k];
int resultIndex = 0; // 用于填充结果数组的当前索引
// 从最高频率开始(桶数组的末尾)向前遍历
// 循环条件 resultIndex < k 确保我们只收集 K 个元素
for (int i = bucket.length - 1; i >= 0 && resultIndex < k; i--) {
if (bucket[i] != null) { // 检查当前频率桶是否为空
for (int element : bucket[i]) { // 遍历当前频率桶中的所有数字
result[resultIndex++] = element; // 将数字添加到结果数组
if (resultIndex == k) { // 如果已收集到 K 个元素,则立即返回结果
return result;
}
}
}
}
return result; // 正常情况下,当 resultIndex 达到 k 时会提前返回
}
}时间复杂度:
空间复杂度:
“前 K 个高频元素”问题通过哈希表和桶排序的组合,提供了一个高效且直观的解决方案。其中,理解并正确实现频率桶的填充过程是成功的关键。始终记住,频率桶应该存储唯一的数字,因此在将数字放入桶中时,必须基于哈希表的键集进行迭代,而非原始数组。这种方法在时间和空间复杂度上都达到了线性级别,使其成为解决此类问题的优秀选择。
以上就是高效解决“前 K 个高频元素”问题:基于桶排序的Java实现与关键细节解析的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号