LeetCode K个高频元素:桶排序算法与关键细节解析

LeetCode K个高频元素:桶排序算法与关键细节解析

本文深入探讨了“k个高频元素”问题的桶排序解法。通过使用哈希映射统计元素频率,并利用数组作为桶(索引为频率,存储对应频率的元素列表),该方法能高效找出前k个出现频率最高的元素。文章着重分析了在填充桶时遍历哈希映射的键集(`keyset()`)而非原始数组的重要性,以确保桶中元素唯一性,避免结果错误。

在算法设计中,高效地从一组数据中找出出现频率最高的K个元素是一个常见且重要的任务。LeetCode上的“K个高频元素”问题正是对此类场景的典型考量。本文将详细介绍一种基于哈希映射(HashMap)和桶排序(Bucket Sort)的解决方案,并特别强调在实现过程中一个关键的细节——如何正确地填充桶。

算法核心思想

解决“K个高频元素”问题通常可以分为两个主要阶段:

频率统计: 首先,我们需要遍历输入数组 nums,统计每个数字出现的频率。这可以通过使用一个哈希映射(HashMap)来实现,其中键是数组中的数字,值是该数字出现的次数。桶排序与结果收集: 接下来,我们创建一个“桶”结构,通常是一个 List[] 数组。这个数组的索引代表元素的频率,而每个索引处存储的 List 则包含所有具有该频率的数字。例如,bucket[2] 将存储所有出现频率为2的数字。由于我们希望找到频率最高的K个元素,我们可以从桶数组的末尾(即最高频率)开始向前遍历,依次收集元素,直到收集到K个为止。

频率统计阶段

这一阶段相对直观。我们初始化一个 HashMap,然后遍历输入数组 nums。对于 nums 中的每一个数字 n,我们更新其在 map 中的频率。如果 n 首次出现,其频率初始化为1;否则,在原有频率上加1。

// the frequency of each element stored in map. var map = new HashMap(); for(int n : nums) {    map.put(n, map.getOrDefault(n, 0) + 1); }

桶排序阶段:填充桶的关键细节

在频率统计完成后,我们需要将这些数字根据它们的频率放入对应的桶中。这一步是整个算法中一个容易出错但至关重要的环节。

桶的定义如下:List[] bucket = new ArrayList[nums.length + 1];。这里的 nums.length + 1 是因为频率的最大值可能等于 nums.length(当所有元素都相同时),所以需要一个足够大的数组来容纳所有可能的频率作为索引。

现在,问题来了:我们应该遍历 nums 数组还是 map.keySet() 来填充桶?

为什么必须遍历 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();     }    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。

存了个图 存了个图

视频图片解析/字幕/剪辑,视频高清保存/图片源图提取

存了个图 17 查看详情 存了个图 对于 1 (freq=2),bucket[2] 中添加 1。对于 2 (freq=2),bucket[2] 中添加 2。对于 3 (freq=1),bucket[1] 中添加 3。最终 bucket[2] 包含 [1, 2],bucket[1] 包含 [3],完美符合预期。

为什么不能遍历 nums 数组

如果尝试遍历原始的 nums 数组来填充桶,将会导致错误的结果:

// 错误的填充桶方式for(int n : nums) { // 遍历原始数组,可能包含重复数字    int freq = map.get(n);    if(bucket[freq] == null) {        bucket[freq] = new ArrayList();     }    bucket[freq].add(n); }

错误分析:继续使用 [1, 1, 2, 2, 3] 的例子。

第一次遇到 1 (freq=2),bucket[2] 中添加 1。第二次遇到 1 (freq=2),bucket[2] 中再次添加 1。第一次遇到 2 (freq=2),bucket[2] 中添加 2。第二次遇到 2 (freq=2),bucket[2] 中再次添加 2。遇到 3 (freq=1),bucket[1] 中添加 3。最终 bucket[2] 可能会变成 [1, 1, 2, 2]。当我们在收集结果时,如果 k=2,我们从 bucket[2] 中取出 1 和 1,这显然是错误的,因为 1 应该只被计作一个不同的高频元素。bucket 中的每个 List 应该只包含唯一的数字。

完整代码示例

结合上述分析,以下是“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 map = new HashMap();         for(int n : nums) {            map.put(n, map.getOrDefault(n, 0) + 1);         }        // 2. 桶排序:创建一个List数组作为桶,索引代表频率        // 桶的长度为 nums.length + 1,因为最大频率可能等于 nums.length        List[] 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):遍历 nums 数组一次,O(N),其中 N 是 nums 的长度。填充桶:遍历 map.keySet(),最多有 N 个不同的元素,每次操作 O(1),所以也是 O(N)。结果收集:最坏情况下,可能需要遍历所有桶和所有元素才能找到 K 个,但每个元素只会被访问一次,所以也是 O(N)。综合时间复杂度为 O(N)

空间复杂度:

HashMap:最坏情况下,所有元素都不同,存储 N 个键值对,O(N)。bucket 数组:存储 N 个列表,所有元素也都在列表中,O(N)。综合空间复杂度为 O(N)

桶中元素唯一性: 再次强调,在填充桶时,必须遍历 HashMap 的键集 (map.keySet()),以确保每个桶中存储的都是唯一的数字。这是避免结果错误的关键。

适用场景: 桶排序方法在处理频率范围相对较小(与元素数量N大致相同)的问题时表现优秀。如果频率范围极大,桶数组会非常稀疏,可能造成空间浪费,但对于 LeetCode 的这类问题,通常频率范围在 [0, N] 之间,因此是高效的选择。

通过理解并正确应用哈希映射和桶排序的原理,特别是对填充桶这一关键步骤的细致处理,我们可以高效且准确地解决“K个高频元素”这类问题。

以上就是LeetCode K个高频元素:桶排序算法与关键细节解析的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/297817.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月4日 23:36:37
下一篇 2025年11月4日 23:41:31

相关推荐

  • C++ 函数名中使用哪些字符是不允许的?

    以下字符不允许出现在 c++++ 函数名中:关键字(如 int、void、bool 等)特殊符号(如 #、%、&、*、- 等)空格(函数名不得包含空格)例外:下划线 (_) 允许用作函数名中的字符美元符号 ($) 和范围运算符 (::) 仅允许用在类的成员函数中 C++ 函数名中不允许使用的…

    2025年12月18日
    000
  • C++ 自身函数在游戏开发中的应用

    c++++ 内置函数在游戏开发中广泛应用,包括:输入/输出:cin/cout、ifstream/ofstream容器:vector、list、map算法:sort、find、random_shuffle实战案例:加载纹理时使用 texture.loadfromfile() 函数加载纹理,并通过 se…

    2025年12月18日
    000
  • C++ 自身函数详解及应用:嵌入式系统编程

    c++++ 内置函数提供了常用功能的实现,简化了嵌入式系统编程。这些函数包括:输入输出(std::cin、std::cout、std::endl)容器(std::string、std::vector、std::map)数据处理(std::algorithm)应用控制流(if、else、while)内…

    2025年12月18日
    000
  • C++ 自身函数详解及应用:设计模式与软件设计

    c++++ 自身函数在设计模式和软件设计中发挥重要作用,包括容器类函数(容器操作)和算法类函数(元素操作)。实战案例展示了如何使用这些函数实现单例模式、工厂模式和迭代器模式。c++ 自身函数的灵活性和功能性,使开发人员能够高效并可靠地编写高质量代码。 C++ 自身函数详解及应用:设计模式与软件设计 …

    2025年12月18日
    000
  • C++ 标准模板库在不同领域的应用

    c++++ 标准模板库 (stl) 应用广泛stl 提供了容器(如数组)、算法和迭代器,可应用于数据结构(数组和链表)、算法(排序和查找)、数据处理(字符串操作和数据转换),为 c++ 代码开发提供了高效和可重用的工具。 C++ 标准模板库在不同领域的应用 引言 C++ 标准模板库 (STL) 是一…

    2025年12月18日
    000
  • C++ 容器类函数的深入分析

    c++++ 容器类函数包括:std::vector:push_back():在末尾添加元素pop_back():删除最后一个元素front():获取第一个元素back():获取最后一个元素std::map:insert():插入键值对erase():删除元素find():查找键 C++ 容器类函数的…

    2025年12月18日
    000
  • C++ 标准模板库的应用案例解析

    c++++ 标准模板库 (stl) 是一组功能强大的数据结构和算法,可简化复杂数据的操作。容器:存储和组织数据,包括数组、链表、集合和映射。算法:对容器中的元素执行操作,例如排序、搜索、转换和累加。实战案例:联系人管理系统:使用容器存储联系人,使用算法搜索和删除联系人。结论:stl 简化了数据操作,…

    2025年12月18日
    000
  • C++ 自身函数详解及应用:map 容器如何高效存储键值对?

    在 c++++ 中,map 容器用于高效存储键值对,确保键的唯一性,并提供多种函数来操作和管理其内容,包括插入、删除和查找键值对。 这些函数包括 begin()、end()、clear()、count()、emplace()、erase()、find()、insert() 和 operator[]。…

    2025年12月18日
    000
  • C++ 函数库中有哪些常用函数?

    c++++ 标准函数库提供多种常用函数,包括:输入/输出:std::cin、std::cout、std::getline容器:std::vector、std::map、std::set算法:std::sort、std::find、std::max字符串:std::string、std::strlen…

    2025年12月18日
    000
  • C++ 自身函数在金融建模中的应用场景有哪些?

    c++++ 自身函数在金融建模中有广泛应用:数学计算:log10、exp、sqrt 等函数用于计算投资回报率、复利等。数据处理:sort、max、min 等函数用于对金融数据进行排序和分析。i/o 操作:ifstream、ofstream、cout、cin 等函数用于文件读写和控制台交互。高级功能:…

    2025年12月18日
    000
  • C++ 函数库与标准模板库如何简化数据结构的实现?

    c++++ 函数库和 stl 简化数据结构实现,它们提供:动态数组(vector)管理内存和跟踪大小fifo 队列(queue)处理排队数据lifo 栈(stack)处理后入先出数据二叉查找树(map)快速查找和插入操作基于键 C++ 函数库与标准模板库(STL)如何简化数据结构的实现 引言 C++…

    2025年12月18日
    000
  • C++ 函数库与标准模板库在算法优化中的应用实例

    c++++ 函数库和 stl 提供丰富的函数和容器,可优化算法。应用包括使用 std::sort 排序数组,使用 std::count 统计元素出现次数,使用 std::find_if 查找满足条件的元素。容器类可优化数据结构,例如使用 vector 管理动态数组和使用 map 优化键值对存储。综合…

    2025年12月18日
    000
  • unordered_map遍历顺序

    unordered_map 遍历顺序是未定义的,可通过迭代器、for-each 循环或 find() 函数进行遍历。影响顺序的因素包括 hash 函数和桶大小,但无法依赖特定顺序。 unordered_map 遍历顺序 unordered_map 是一种无序关联容器,这意味着其元素的顺序是未定义的。…

    2025年12月18日
    000
  • unordered_map的特性

    unordered_map是一种哈希表实现的关联容器,具有快速插入和查找操作,键唯一,无序存储,可迭代,并使用键比较函数和负载因子优化性能,优点是查找和插入速度快,但键无序,哈希冲突可能会影响性能。 unordered_map 的特性 unordered_map 是 C++ 标准库中的一种关联容器,…

    2025年12月18日
    000
  • unordered_map默认值

    unordered_map是一种基于哈希表的关联容器,不保证键的排序,但提供高效的键值存储。默认情况下,未插入的键返回其值的类型的默认值,例如int键和double值的默认值分别为0和0.0。您可以通过插入、emplace或默认构造函数设置自定义默认值。 unordered_map默认值 unord…

    2025年12月18日
    000
  • unordered_map的作用

    unordered_map是一种C++容器,用于通过哈希表快速查找和插入键值对。主要优点包括O(1)平均复杂度、适用于大数据集;缺点是键顺序不确定、可能发生哈希冲突。适用于需要快速查找和插入,以及元素数量不确定的场景,如缓存系统、数据库和图形数据库。 unordered_map 的作用 unorde…

    2025年12月18日
    000
  • unordered_map 的参数

    unordered_map 的构造参数包括:1. 键类型、2. 值类型、3. 哈希函数、4. 键相等比较函数、5. 分配器。这些参数用于定义 map 中元素的存储和访问方式。例如,可以创建使用 int 作为键类型和 string 作为值类型的 unordered_map,然后使用 my_map.in…

    2025年12月18日
    000
  • unordered_map 的函数

    unordered_map 提供了以下常用的函数:查找操作:[] 和 at() 返回键值引用,count() 返回键关联元素数量,find() 返回键关联迭代器;插入操作:insert() 插入键值对,emplace() 仅在键不存在时插入;删除操作:erase() 删除键关联元素,clear() …

    2025年12月18日
    000
  • unordered_map添加元素

    要向 C++ 的 unordered_map 添加元素,可以使用以下两种方法:通过 emplace() 方法直接构造新元素。通过 [] 操作符查找或创建新元素并进行赋值。 unordered_map 添加元素 unordered_map 是 C++ 标准库中用于存储键值对的关联容器,它使用哈希表来实…

    2025年12月18日
    000
  • unordered_map的用法详细讲解

    unordered_map 是一种哈希表数据结构,通过哈希函数将键值对映射到索引。它支持快速插入(O(1) 平均时间复杂度)、查找和访问,并且占用空间小。然而,哈希函数可能会导致碰撞,并且元素的存储顺序是无序的。 unordered_map 用法详细讲解 什么是 unordered_map? uno…

    2025年12月18日
    000

发表回复

登录后才能评论
关注微信