讲师中心 微信公众号
AI工具推荐 视频效率加速

Top K 频繁元素:桶排序算法深度解析与实现要点

梦强酱_6721

梦强酱_6721

发布时间:2025-11-01 13:46:00

|

627人浏览过

|

来源于php中文网

原创

Top K 频繁元素:桶排序算法深度解析与实现要点

本文深入探讨了如何使用桶排序算法高效解决“top k 频繁元素”问题。文章首先概述了问题背景,随后详细阐述了通过哈希表统计元素频率,再结合桶排序将元素按频率分组的核心思路。特别强调了在构建频率桶时,遍历哈希表的键集(`keyset()`)而非原始数组(`nums`)的重要性,以确保桶中存储的元素是唯一的。最后,提供了完整的 java 解决方案代码及详细解析,并总结了关键实现要点。

引言:理解 Top K 频繁元素问题

“Top K 频繁元素”是一个常见的算法问题,要求从一个整数数组中找出出现频率最高的 K 个元素。例如,给定数组 [1,1,1,2,2,3] 和 K=2,我们期望的输出是 [1,2],因为 1 出现了 3 次,2 出现了 2 次,它们是频率最高的两个元素。解决这类问题通常需要高效地统计元素频率,并在此基础上进行排序或选择。

核心思路:频率统计与桶排序

解决“Top K 频繁元素”问题的一个高效方法是结合使用哈希表进行频率统计和桶排序(Bucket Sort)进行元素分组。

第一步:使用哈希表统计元素频率

首先,我们需要遍历输入数组 nums,统计每个元素出现的次数。哈希表(HashMap 在 Java 中)是实现这一目标的理想数据结构,它的键(Key)存储数组中的元素,值(Value)存储该元素的出现频率。

// 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);
}

这段代码遍历 nums 数组,对于每个元素 n,如果它已存在于 map 中,则将其频率加 1;否则,将其添加到 map 中,并将频率初始化为 1。

第二步:构建频率桶

在统计完所有元素的频率后,我们可以创建一个“桶”数组。这个桶数组是一个 List<Integer>[] 类型,其中数组的索引代表元素的频率,而该索引处存储的 List 则包含所有具有该频率的元素。例如,bucket[3] 将是一个列表,其中包含所有出现了 3 次的元素。桶数组的大小通常为 nums.length + 1,因为元素的最高频率不会超过数组的长度。

List<Integer>[] bucket = new ArrayList[nums.length + 1];

关键抉择:遍历 keySet() 与遍历原始数组 nums 的区别

在将元素放入频率桶时,一个常见的疑问是:应该遍历哈希表的键集(map.keySet())还是原始数组(nums)?这正是本问题中的核心困惑点。

为什么选择 map.keySet()

正确的做法是遍历 map.keySet()。map.keySet() 返回的是哈希表中所有唯一键的集合。这意味着我们只会处理每个不同的元素一次。

Alibabacloud Sdk Client Initialization For Java
Alibabacloud Sdk Client Initialization For Java

在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。

下载
for(int n : map.keySet()) {
    int freq = map.get(n); // 获取元素 n 的频率
    if(bucket[freq] == null) {
        bucket[freq] = new ArrayList<Integer>();
    }
    bucket[freq].add(n); // 将元素 n 添加到对应频率的桶中
}

通过这种方式,每个唯一的元素 n 会被精确地放入它对应的频率桶中。例如,如果数字 1 出现了 3 次,它会且仅会出现在 bucket[3] 中一次。

为什么不能直接遍历 nums

如果选择遍历原始数组 nums 来填充桶,将会导致错误的结果。

// 错误示例:
for(int n : nums) {
    int freq = map.get(n);
    if(bucket[freq] == null) {
        bucket[freq] = new ArrayList<Integer>();
    }
    bucket[freq].add(n); // 问题:同一个元素 n 会被多次添加到桶中
}

考虑数组 [1,1,1,2,2,3]。 当 n 是第一个 1 时,freq 为 3,1 被添加到 bucket[3]。 当 n 是第二个 1 时,freq 仍为 3,1 再次被添加到 bucket[3]。 当 n 是第三个 1 时,freq 仍为 3,1 第三次被添加到 bucket[3]。 最终,bucket[3] 中会包含 [1, 1, 1]。

然而,我们希望的是 bucket[3] 只包含一个 1,因为 1 是一个独立的元素,其频率为 3。问题的要求是找出“Top K 频繁元素”,这些元素本身应该是独特的。如果桶中包含重复的元素,那么在后续从桶中收集结果时,for(int element : bucket[i]) 循环将会把这些重复的元素也添加到最终结果中,导致输出不正确(可能包含重复元素或超出 K 个元素)。

完整 Java 解决方案详解

结合上述思路,以下是解决“Top K 频繁元素”问题的完整 Java 解决方案:

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        // 1. 初始化频率桶数组。
        // 数组索引代表频率,List 存储具有该频率的元素。
        // 最大频率不会超过 nums.length,所以大小为 nums.length + 1。
        List<Integer>[] bucket = new ArrayList[nums.length + 1];

        // 2. 使用 HashMap 统计每个元素的频率。
        var map = new HashMap<Integer, Integer>();
        for(int n : nums) {
            map.put(n, map.getOrDefault(n, 0) + 1);
        }

        // 3. 遍历 HashMap 的键集,将每个唯一的元素放入其对应频率的桶中。
        for(int n : map.keySet()) {
            int freq = map.get(n); // 获取元素 n 的频率
            if(bucket[freq] == null) {
                bucket[freq] = new ArrayList<Integer>();
            }
            bucket[freq].add(n); // 将元素 n 添加到 bucket[freq] 列表中
        }

        // 4. 从高频率到低频率遍历桶,收集 Top K 频繁元素。
        int[] res = new int[k];
        int resIndex = 0; // 结果数组的当前填充位置
        int counter = 0;  // 已收集到的频繁元素数量

        // 从 bucket 数组的末尾(最高频率)开始向前遍历
        for(int i = bucket.length - 1; i >= 0; i--) {
            if(bucket[i] != null) { // 如果当前频率的桶不为空
                for(int element : bucket[i]) { // 遍历桶中的所有元素
                    res[counter++] = element; // 将元素添加到结果数组
                    if(counter == k) { // 如果已收集到 K 个元素,则返回结果
                        return res;
                    }
                }
            }
        }
        return res; // 理论上不会执行到这里,因为题目保证 K 是有效的
    }
}

代码解析:

  1. List<Integer>[] bucket = new ArrayList[nums.length + 1];: 初始化一个 ArrayList 数组作为桶。数组的每个位置 i 将存储一个 ArrayList,其中包含所有频率为 i 的元素。
  2. var map = new HashMap<Integer, Integer>();: 创建哈希表 map,用于存储 元素 -> 频率 的映射。
  3. for(int n : nums) map.put(n, map.getOrDefault(n, 0) + 1);: 遍历输入数组 nums,计算每个元素的频率并存储在 map 中。
  4. for(int n : map.keySet()) { ... }: 这是关键步骤。 遍历 map 的键集,即所有不同的元素。对于每个元素 n,获取其频率 freq,然后将其添加到 bucket[freq] 对应的列表中。这一步确保了每个唯一的元素只被放入其对应的频率桶一次。
  5. for(int i = bucket.length - 1; i >= 0; i--) { ... }: 从桶数组的最高频率(即 bucket.length - 1)开始,向低频率遍历。
  6. if(bucket[i] != null) { ... }: 检查当前频率的桶是否为空。
  7. for(int element : bucket[i]) { ... }: 遍历当前频率桶中的所有元素。由于我们是从高频率开始遍历,这些元素就是频率最高的。
  8. res[counter++] = element;: 将当前元素添加到结果数组 res 中。
  9. if(counter == k) { return res; }: 如果已经收集到了 K 个元素,就立即返回结果数组。

注意事项与总结

  • 唯一性是核心: 在将元素放入频率桶时,务必确保每个不同的元素只被放入一次。这是为什么必须遍历 map.keySet() 而不是原始 nums 数组的原因。
  • 桶数组大小: 桶数组的大小应至少为 nums.length + 1,以容纳所有可能的频率值(从 0 到 nums.length)。
  • 遍历顺序: 为了找到 Top K 频繁元素,我们需要从桶数组的末尾(代表最高频率)开始向前遍历。
  • 时间复杂度:
    • 统计频率:O(N),N 是数组 nums 的长度。
    • 填充桶:O(M),M 是 nums 中不同元素的数量,M <= N。
    • 收集结果:在最坏情况下,需要遍历所有桶和桶中的所有元素,但由于我们只收集 K 个元素,这一步的复杂度通常被 O(N) 或 O(M) 包含。
    • 总时间复杂度:O(N)。
  • 空间复杂度:
    • 哈希表:O(M),M 是不同元素的数量。
    • 桶数组:O(N)。
    • 总空间复杂度:O(N)。

通过上述方法,我们能够以线性的时间复杂度高效地解决“Top K 频繁元素”问题,并且清晰地理解了在构建频率桶时处理元素唯一性的重要性。

热门AI工具

更多
豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

VibeKnow
VibeKnow Hot

一款AI视频创作工具,主要用于全球首个AI知识视频创作平台,文档、文章、网页,一键生成视频,适合需要提升相关任务效率的用户。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

音述AI
音述AI Hot

一款AI音频处理工具,主要用于音述AI是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区,适合需要提升相关任务效率的用户。

相关专题

更多
c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

509

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1638

2024.03.01

sort排序函数用法
sort排序函数用法

sort排序函数的用法:1、对列表进行排序,默认情况下,sort函数按升序排序,因此最终输出的结果是按从小到大的顺序排列的;2、对元组进行排序,默认情况下,sort函数按元素的大小进行排序,因此最终输出的结果是按从小到大的顺序排列的;3、对字典进行排序,由于字典是无序的,因此排序后的结果仍然是原来的字典,使用一个lambda表达式作为key参数的值,用于指定排序的依据。

1098

2023.09.04

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

4999

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2565

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

3108

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

2185

2025.08.29

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2081

2023.12.01

AI视频生成软件推荐
AI视频生成软件推荐

本专题汇总了当前主流的AI视频生成软件推荐与排行榜单,涵盖seko、AniShort、剧云、Lovart、LiblibAI及立刻mv等热门工具。同时整理了各软件在文生视频、图生视频、时长限制、画质表现及免费额度等方面的差异对比,助您快速选对适合创作需求的AI视频生成工具。

160

2026.09.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
dev.java 官方:Learn Java
dev.java 官方:Learn Java

共0课时 | 0人学习

Java JDBC数据库连接官方教程
Java JDBC数据库连接官方教程

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn