
本文深入探讨了“k个高频元素”问题的桶排序解法。通过使用哈希映射统计元素频率,并利用数组作为桶(索引为频率,存储对应频率的元素列表),该方法能高效找出前k个出现频率最高的元素。文章着重分析了在填充桶时遍历哈希映射的键集(`keyset()`)而非原始数组的重要性,以确保桶中元素唯一性,避免结果错误。
在算法设计中,高效地从一组数据中找出出现频率最高的K个元素是一个常见且重要的任务。LeetCode上的“K个高频元素”问题正是对此类场景的典型考量。本文将详细介绍一种基于哈希映射(HashMap)和桶排序(Bucket Sort)的解决方案,并特别强调在实现过程中一个关键的细节——如何正确地填充桶。
解决“K个高频元素”问题通常可以分为两个主要阶段:
这一阶段相对直观。我们初始化一个 HashMap,然后遍历输入数组 nums。对于 nums 中的每一个数字 n,我们更新其在 map 中的频率。如果 n 首次出现,其频率初始化为1;否则,在原有频率上加1。
// the frequency of each element stored in map.
var map = new HashMap<Integer, Integer>();
for(int n : nums) {
map.put(n, map.getOrDefault(n, 0) + 1);
}在频率统计完成后,我们需要将这些数字根据它们的频率放入对应的桶中。这一步是整个算法中一个容易出错但至关重要的环节。
桶的定义如下:List<Integer>[] bucket = new ArrayList[nums.length + 1];。这里的 nums.length + 1 是因为频率的最大值可能等于 nums.length(当所有元素都相同时),所以需要一个足够大的数组来容纳所有可能的频率作为索引。
现在,问题来了:我们应该遍历 nums 数组还是 map.keySet() 来填充桶?
正确的做法是遍历 map.keySet()。map.keySet() 返回的是哈希映射中所有唯一的键(即输入数组中所有不重复的数字)。对于每一个唯一的数字 n,我们获取其在 map 中对应的频率 freq,然后将 n 添加到 bucket[freq] 对应的列表中。
// 正确的填充桶方式
for(int n : map.keySet()) { // 遍历唯一的数字
int freq = map.get(n);
if(bucket[freq] == null) {
bucket[freq] = new ArrayList<Integer>();
}
bucket[freq].add(n);
}原因分析: 桶 bucket[freq] 应该存储的是“所有出现频率为 freq 的唯一数字”。题目要求返回的是K个不同的高频元素。如果 bucket[freq] 中包含了重复的数字,那么在后续收集结果时,我们会错误地将同一个数字多次计入结果,或者导致最终结果包含非去重元素,从而不符合题目要求。
map.keySet() 保证了我们每次处理的都是一个唯一的数字。例如,如果输入是 [1, 1, 2, 2, 3],map 会是 {1:2, 2:2, 3:1}。遍历 map.keySet() 时,我们会依次处理 1、2、3。
如果尝试遍历原始的 nums 数组来填充桶,将会导致错误的结果:
// 错误的填充桶方式
for(int n : nums) { // 遍历原始数组,可能包含重复数字
int freq = map.get(n);
if(bucket[freq] == null) {
bucket[freq] = new ArrayList<Integer>();
}
bucket[freq].add(n);
}错误分析: 继续使用 [1, 1, 2, 2, 3] 的例子。
结合上述分析,以下是“K个高频元素”问题的完整Java解决方案:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// 1. 频率统计:使用HashMap统计每个数字的出现频率
Map<Integer, Integer> map = new HashMap<>();
for(int n : nums) {
map.put(n, map.getOrDefault(n, 0) + 1);
}
// 2. 桶排序:创建一个List数组作为桶,索引代表频率
// 桶的长度为 nums.length + 1,因为最大频率可能等于 nums.length
List<Integer>[] bucket = new ArrayList[nums.length + 1];
// 3. 填充桶:遍历map的keySet(),将唯一的数字根据其频率放入对应的桶中
// 这一步是关键,确保桶中存储的是唯一的数字
for(int n : map.keySet()) {
int freq = map.get(n);
if(bucket[freq] == null) {
bucket[freq] = new ArrayList<>();
}
bucket[freq].add(n);
}
// 4. 收集结果:从频率最高的桶开始逆序遍历,收集前K个元素
int[] result = new int[k];
int resultIndex = 0; // 用于填充结果数组的索引
// 从最高频率(bucket.length - 1)开始向下遍历
for(int i = bucket.length - 1; i >= 0; i--) {
if(bucket[i] != null) { // 如果当前频率的桶不为空
for(int element : bucket[i]) { // 遍历桶中的每个元素
result[resultIndex++] = element;
if(resultIndex == k) { // 如果已收集到K个元素,则返回
return result;
}
}
}
}
return result; // 理论上不会执行到这里,因为题目保证K是有效的
}
}时间复杂度:
空间复杂度:
桶中元素唯一性: 再次强调,在填充桶时,必须遍历 HashMap 的键集 (map.keySet()),以确保每个桶中存储的都是唯一的数字。这是避免结果错误的关键。
适用场景: 桶排序方法在处理频率范围相对较小(与元素数量N大致相同)的问题时表现优秀。如果频率范围极大,桶数组会非常稀疏,可能造成空间浪费,但对于 LeetCode 的这类问题,通常频率范围在 [0, N] 之间,因此是高效的选择。
通过理解并正确应用哈希映射和桶排序的原理,特别是对填充桶这一关键步骤的细致处理,我们可以高效且准确地解决“K个高频元素”这类问题。
以上就是LeetCode K个高频元素:桶排序算法与关键细节解析的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号