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

Python实现K个高频元素:从频率计数到高效算法

阿萱小哥_1133

阿萱小哥_1133

发布时间:2025-11-16 11:58:24

|

1026人浏览过

|

来源于php中文网

原创

python实现k个高频元素:从频率计数到高效算法

本文详细介绍了如何在Python中高效地统计数组中元素的出现频率,这是解决“K个高频元素”问题的关键一步。我们将探讨使用哈希映射(字典)进行计数的正确方法,纠正常见编码错误,并为读者提供清晰的示例代码。在此基础上,文章进一步讲解了如何利用排序和最小堆两种策略,从频率统计结果中筛选出K个最高频率元素,并给出了完整的解决方案及注意事项。

理解“K个高频元素”问题

“K个高频元素”问题是一个常见的算法挑战,其目标是从一个给定的整数数组 nums 中,找出出现频率最高的 k 个元素。元素的返回顺序可以不固定。

例如:

  • 给定 nums = [1, 1, 1, 2, 2, 3] 和 k = 2。
  • 元素 1 出现 3 次。
  • 元素 2 出现 2 次。
  • 元素 3 出现 1 次。
  • 频率最高的 2 个元素是 1 和 2。

解决这个问题通常分为两个主要步骤:

立即学习“Python免费学习笔记(深入)”;

  1. 统计数组中每个元素的出现频率。
  2. 根据频率从高到低筛选出前 k 个元素。

步骤一:高效统计元素频率

统计元素频率最直观且高效的方法是使用哈希映射(在Python中即为字典)。哈希映射可以提供接近 O(1) 的平均时间复杂度进行插入和查找操作。

常见错误与修正

在实现频率统计时,一个常见的错误是混淆迭代器变量的含义。考虑以下错误代码示例:

nums = [1, 1, 1, 2, 2, 3]
iterations = {}

for x in nums:
    # 错误:x是数组中的元素值,而非索引。
    # nums[x] 会尝试将元素值作为索引去访问数组,这会导致IndexError或逻辑错误。
    if nums[x] in iterations:
        iterations[nums[x]] += 1
    else:
        iterations[nums[x]] = 1

print(iterations) # 对于 nums = [1,1,1,2,2,3],可能输出 {1: 5, 2: 1} 或抛出 IndexError

上述代码的问题在于,for x in nums: 循环中的 x 直接代表了 nums 列表中的每个元素的值,而不是它的索引。因此,正确的做法是直接使用 x 作为字典的键来统计其频率,而不是 nums[x]。

修正后的频率统计代码:

nums = [1, 1, 1, 2, 2, 3]
frequency_map = {} # 使用更具描述性的变量名

for x in nums:
    if x in frequency_map:
        frequency_map[x] += 1
    else:
        frequency_map[x] = 1

print(frequency_map) # 正确输出: {1: 3, 2: 2, 3: 1}

这段代码遍历 nums 列表,对于每个元素 x:

  • 如果 x 已经在 frequency_map 中作为键存在,则将其对应的频率值加 1。
  • 如果 x 不在 frequency_map 中,则将其添加为新键,并将频率初始化为 1。

更Pythonic的频率统计方法:使用 collections.Counter

Python标准库 collections 模块提供了一个 Counter 类,它专门用于高效地计数可哈希对象的出现次数。这是处理频率统计任务时最推荐的方法。

Python Testing
Python Testing

Python 测试速查:运行 pytest、使用 mock/patch、参数化、fixtures、异步、覆盖率测试。

下载
from collections import Counter

nums = [1, 1, 1, 2, 2, 3]
frequency_map = Counter(nums)

print(frequency_map) # 输出: Counter({1: 3, 2: 2, 3: 1})

Counter 对象是一个字典的子类,它以元素为键,以其出现次数为值。其初始化和使用都非常简洁高效。

步骤二:筛选K个高频元素

在得到元素的频率映射 frequency_map 后,我们需要从中选出频率最高的 k 个元素。这里介绍两种常用的方法:

方法一:排序法

最直接的方法是将频率映射转换为一个列表,然后根据频率进行排序,最后取出前 k 个元素。

# 假设 frequency_map = {1: 3, 2: 2, 3: 1}
k = 2

# 将字典转换为 (元素, 频率) 元组列表
# 例如:[(1, 3), (2, 2), (3, 1)]
items = list(frequency_map.items())

# 根据频率(元组的第二个元素)进行降序排序
# key=lambda item: item[1] 指定排序依据是元组的第二个元素(频率)
# reverse=True 表示降序排列
items.sort(key=lambda item: item[1], reverse=True)

# 提取前 k 个元素的键(即元素本身)
top_k_elements = [item[0] for item in items[:k]]

print(top_k_elements) # 输出: [1, 2]

时间复杂度分析:

  • 将字典转换为列表:O(N),其中 N 是唯一元素的数量。
  • 排序:O(N log N)。
  • 提取前 k 个元素:O(k)。
  • 总时间复杂度:O(N log N)。

方法二:最小堆(优先队列)法

当 k 远小于唯一元素的总数 N 时,使用最小堆(min-heap)通常比完全排序更高效。最小堆可以帮助我们维护一个大小为 k 的数据结构,其中包含当前频率最高的 k 个元素。

基本思想:

  1. 创建一个大小为 k 的最小堆。堆中存储 (频率, 元素) 对。
  2. 遍历 frequency_map 中的每个 (元素, 频率) 对。
  3. 将 (频率, 元素) 对推入最小堆。
  4. 如果堆的大小超过 k,则弹出堆顶元素(即当前堆中频率最小的元素)。
  5. 遍历结束后,堆中剩下的 k 个元素就是频率最高的 k 个元素。
import heapq

# 假设 frequency_map = {1: 3, 2: 2, 3: 1}
k = 2

min_heap = [] # 堆中存储 (频率, 元素) 元组

for num, freq in frequency_map.items():
    # 将 (频率, 元素) 推入最小堆
    heapq.heappush(min_heap, (freq, num))
    # 如果堆的大小超过 k,则弹出堆中频率最小的元素
    if len(min_heap) > k:
        heapq.heappop(min_heap)

# 提取堆中剩余的元素(只取元素本身)
top_k_elements_heap = [item[1] for item in min_heap]

print(top_k_elements_heap) # 输出: [2, 1] (顺序可能因堆实现而异)

时间复杂度分析:

  • 遍历 frequency_map:O(N),其中 N 是唯一元素的数量。
  • 每次堆操作(heappush 或 heappop):O(log k)。
  • 总时间复杂度:O(N log k)。

当 k 远小于 N 时,O(N log k) 优于 O(N log N)。

完整解决方案示例

结合上述步骤,以下是使用 collections.Counter 和最小堆的完整 Python 解决方案:

from collections import Counter
import heapq

def topKFrequent(nums: list[int], k: int) -> list[int]:
    """
    找出给定整数数组中出现频率最高的 k 个元素。

    Args:
        nums: 整数数组。
        k: 需要找出的高频元素数量。

    Returns:
        包含 k 个高频元素的列表。
    """
    if not nums:
        return []

    # 步骤1: 统计元素频率
    # 使用 collections.Counter 简洁高效地完成频率统计
    frequency_map = Counter(nums)

    # 步骤2: 使用最小堆找出 K 个高频元素
    # 堆中存储 (频率, 元素) 元组,最小堆会根据元组的第一个元素(频率)进行排序
    min_heap = [] 

    for num, freq in frequency_map.items():
        # 将当前元素的 (频率, 元素) 对推入最小堆
        heapq.heappush(min_heap, (freq, num))

        # 如果堆的大小超过 k,则弹出堆顶元素(即当前堆中频率最小的元素)
        if len(min_heap) > k:
            heapq.heappop(min_heap)

    # 提取堆中剩余的元素(只取元素本身)
    # 此时堆中保留的就是频率最高的 k 个元素
    result = [item[1] for item in min_heap]

    return result

# 示例测试
nums1 = [1, 1, 1, 2, 2, 3]
k1 = 2
print(f"输入: nums={nums1}, k={k1} -> 输出: {topKFrequent(nums1, k1)}") 
# 预期输出可能为 [1, 2] 或 [2, 1],因为顺序不重要

nums2 = [1]
k2 = 1
print(f"输入: nums={nums2}, k={k2} -> 输出: {topKFrequent(nums2, k2)}") 

nums3 = [3, 0, 1, 0]
k3 = 1
print(f"输入: nums={nums3}, k={k3} -> 输出: {topKFrequent(nums3, k3)}") 

nums4 = [1, 2]
k4 = 2
print(f"输入: nums={nums4}, k={k4} -> 输出: {topKFrequent(nums4, k4)}") 

注意事项与总结

  1. 理解迭代行为: 在 Python 中,for item in collection: 结构会直接遍历 collection 中的每个 item,而不是索引。这是初学者常犯的错误点,务必区分 item 和 collection[item] 的含义。
  2. 选择合适的数据结构: 对于频率统计,collections.Counter 是 Python 中最简洁和高效的选择。对于需要动态维护前 k 个最大/最小元素的场景,堆(优先队列)是理想的数据结构。
  3. 时间复杂度考量:
    • 简单排序法的时间复杂度为 O(N log N),其中 N 是唯一元素的数量。
    • 最小堆法的时间复杂度为 O(N log k),其中 N 是唯一元素的数量,k 是目标数量。当 k 远小于 N 时,堆法效率更高。
  4. 边缘情况处理: 在实际应用中,需要考虑输入数组为空、k 值小于等于 0、k 值大于唯一元素数量等边缘情况。上述 topKFrequent 函数已包含对空数组的简单处理。
  5. 输出顺序: 问题通常不要求高频元素的特定输出顺序,因此排序和堆方法返回的顺序可能不同,但这不影响结果的正确性。

通过掌握频率统计的正确方法和高效的筛选策略,您可以有效地解决“K个高频元素”这类问题,并为更复杂的算法挑战打下坚实的基础。

热门AI工具

更多
讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

UP简历
UP简历 Hot

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

PixTV
PixTV Hot

PixTV是一款面向AIGC内容创作的AI视频生成工具。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

音述AI
音述AI Hot

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

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

DeepSeek

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

WorkBuddy

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

豆包大模型

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

相关专题

更多
treenode的用法
treenode的用法

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

2241

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

316

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

357

2026.01.06

C++ 数据结构与算法实现教程合集
C++ 数据结构与算法实现教程合集

以 C++ 为实现语言,系统讲解核心数据结构与算法,涵盖链表(单链表/双链表/环检测)、栈与队列(单调栈/优先队列)、二叉树(遍历/BST/AVL/红黑树)、哈希表(开地址法/链地址法)、图(邻接表/BFS/DFS/Dijkstra/拓扑排序)、常见排序算法(快排/归并/堆排/计数排序)的实现与复杂度分析,同时分享 LeetCode 刷题技巧、竞赛编程常用模板(二分/前缀和/滑动窗口/动态规划),帮助开发者夯实算法基础。

412

2026.05.09

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

4867

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

2188

2023.08.10

页面置换算法
页面置换算法

页面置换算法是操作系统中用来决定在内存中哪些页面应该被换出以便为新的页面提供空间的算法。本专题为大家提供页面置换算法的相关文章,大家可以免费体验。

4976

2023.08.14

LLVM自定义Pass怎么写
LLVM自定义Pass怎么写

本专题聚焦LLVM自定义Pass开发,整理Pass类结构、run()方法、PreservedAnalyses、CMake构建、插件注册、-load-pass-plugin加载和测试用例编写流程。

0

2026.09.30

LLVM RISC-V参数配置教程
LLVM RISC-V参数配置教程

本专题介绍LLVM对RISC-V基础ISA和扩展的支持方式,涵盖RV32、RV64、标准扩展、实验性扩展、厂商扩展、-menable-experimental-extensions和版本差异。

0

2026.09.30

热门下载

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

精品课程

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

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