查找热点数据问题| 豆包MarsCode AI 刷题

50 阅读8分钟

一、思路分析

  1. 统计频率

    • 首先,需要知道数组中每个元素出现的频率。这里使用了 Python 标准库 collections 中的 Counter 类来轻松实现这一功能。它会遍历数组 nums,并自动统计每个元素出现的次数,将元素作为键,出现的频率作为值,存储在一个类似字典的结构中。
  2. 利用最小堆筛选前 k 高频率元素

    • 接下来,要找出出现频率前 k 高的元素。这里采用了最小堆(通过 heapq 模块实现)的数据结构。最小堆的特性是堆顶元素始终是堆中最小的元素。
    • 遍历由 Counter 统计得到的每个元素及其频率的键值对。对于每个元素,将其频率和元素值作为一个元组 (freq, num) 压入最小堆 min_heap 中。
    • 在压入元素的过程中,需要检查堆的大小。如果堆的大小超过了 k,就将堆顶元素(即当前堆中频率最小的元素)弹出。这样,在遍历完所有元素后,最小堆中就保留了出现频率前 k 高的元素(虽然此时堆顶是这 k 个元素中频率相对最小的,但整体堆中存储的就是前 k 高频率的元素)。
  3. 排序与输出

    • 从最小堆中提取出这 k 个元素后,得到的列表 top_k 中的元素顺序是按照在堆中的顺序排列的,而这个顺序可能不是按照元素值的升序排列。所以,需要进一步对 top_k 按照元素值进行排序,这里使用了 sort 方法,并通过 key 参数指定按照元组中的第二个元素(即元素值)进行排序。
    • 最后,将排序后的元素转换为字符串形式,并使用逗号连接起来,得到符合题目要求的返回结果。

二、图解(示例以 nums = [4, 4, 4, 2, 2, 2, 3, 3, 1], k = 2 为例)

1. 统计频率阶段

元素频率
11
23
32
43

此时通过 Counter 得到了每个元素及其对应的频率。

2. 利用最小堆筛选阶段

  • 开始遍历上述的键值对并压入最小堆。

    • 首先压入 (1, 1),此时堆中只有一个元素。

    • 接着压入 (3, 2),堆会根据频率进行调整,此时堆顶是 (1, 1)(因为 1 的频率最小)。

    • 再压入 (2, 3),堆再次调整,堆顶依然是 (1, 1)

    • 压入 (3, 4),堆调整后,堆顶还是 (1, 1)

    • 此时堆的大小超过了 k = 2,所以弹出堆顶元素 (1, 1)

    • 继续遍历完剩下的元素,最终堆中保留的是 (2, 3) 和 (3, 4)(这里只是示意,实际顺序由堆的调整规则确定,但肯定是频率相对较高的两个元素对应的元组)。

3. 排序与输出阶段

  • 从堆中取出元素得到 top_k = [(2, 3), (3, 4)]
  • 按照元素值排序后变为 [(3, 4), (2, 3)]
  • 最后转换为字符串并连接得到 "2,4"

三、代码详解

收起

python

复制

from collections import Counter
import heapq

def solution(nums, k):
    # 统计每个元素的频率
    frequency = Counter(nums)

    # 使用最小堆存储频率前 k 高的元素
    min_heap = []

    for num, freq in frequency.items():
        heapq.heappush(min_heap, (freq, num))
        if len(min_heap) > k:
            heapq.heappop(min_heap)

    # 提取堆中的元素,按频率和元素值排序
    top_k = [heapq.heappop(min_heap) for _ in range(len(min_heap))]

    # 按值排序
    top_k.sort(key=lambda x: x[1])

    # 格式化输出
    result = ','.join(str(num) for _, num in top_k)
    return result
  1. frequency = Counter(nums)

    • 这行代码利用 Counter 类统计数组 nums 中每个元素的出现频率。例如,如果 nums = [1, 1, 2],那么 frequency 将会是一个类似字典的对象,其中 {1: 2, 2: 1},表示元素 1 出现了 2 次,元素 2 出现了 1 次。
  2. min_heap = []

    • 创建一个空的列表 min_heap,用于后续构建最小堆。
  3. for num, freq in frequency.items():...

    • 遍历由 Counter 统计得到的每个元素及其频率的键值对。对于每一对 (num, freq)

      • heapq.heappush(min_heap, (freq, num)):将元素的频率和元素值组成的元组 (freq, num) 压入最小堆 min_heap 中。堆会根据元组的第一个元素(即频率)自动调整结构,使得堆顶元素始终是频率最小的那个元组。
      • if len(min_heap) > k::在每次压入元素后,检查堆的大小。如果堆的大小超过了指定的 k 值,就执行 heapq.heappop(min_heap),弹出堆顶元素(即当前堆中频率最小的元素对应的元组),从而保证堆中始终保留的是出现频率前 k 高的元素对应的元组。
  4. top_k = [heapq.heappop(min_heap) for _ in range(len(min_heap))]

    • 通过列表推导式,依次从最小堆中弹出所有元素,并将它们存储在列表 top_k 中。此时 top_k 中的元素顺序是按照在堆中的弹出顺序排列的,不一定是按照元素值的升序排列。
  5. top_k.sort(key=lambda x: x[1])

    • 使用 sort 方法对 top_k 列表进行排序。通过指定 key=lambda x: x[1],表示按照元组中的第二个元素(即元素值)进行排序,使得 top_k 中的元素按照元素值的升序排列。
  6. result = ','.join(str(num) for _, num in top_k)

    • 首先通过列表推导式 (str(num) for _, num in top_k) 将 top_k 中每个元组的第二个元素(即元素值)转换为字符串形式。然后使用 ','.join() 方法将这些字符串用逗号连接起来,得到最终符合题目要求的返回结果字符串。

四、知识总结

新知识点梳理与分析

  1. Counter 类

    • 来自于 collections 库,它提供了一种方便快捷的方式来统计可迭代对象(如列表、字符串等)中每个元素出现的次数。它返回一个类似字典的对象,其中键是元素,值是该元素出现的频率。对于处理需要统计元素出现频率的问题,使用 Counter 可以大大简化代码逻辑。例如,在本题中,如果手动去统计数组中每个元素的频率,需要编写较多的循环和判断逻辑,而 Counter 一行代码就搞定了。
  2. heapq 模块与最小堆

    • heapq 是 Python 标准库中用于实现堆数据结构的模块。堆是一种特殊的树形数据结构,分为最小堆和最大堆。在本题中使用的是最小堆,其特点是堆顶元素始终是堆中最小的元素。通过 heapq 模块的 heappush 和 heappop 操作,可以方便地向堆中插入元素和从堆中弹出元素,并且堆会自动根据元素的大小(在本题中是根据元素频率)进行调整,保持堆的特性。利用最小堆的这种特性,能够高效地筛选出出现频率前 k 高的元素。与其他排序算法(如排序后再取前 k 个元素的方法)相比,在时间复杂度上有优势,尤其是当 k 相对于数组大小 n 较小时。

理解与学习建议

  • 理解

    • 本题的核心在于巧妙地结合了 Counter 统计频率和 heapq 构建最小堆这两个工具来解决找出前 k 高频率元素的问题。Counter 让我们快速得到每个元素的频率信息,而最小堆则帮助我们在不需要对整个数组进行完全排序的情况下,高效地筛选出频率较高的元素。这种分阶段处理问题的思路在很多算法问题中都很常见,先对数据进行某种预处理(如本题的统计频率),然后再利用合适的数据结构(如本题的最小堆)进行进一步的筛选或处理。
  • 学习建议

    • 对于刚入门的同学来说,首先要熟悉 Python 的基本数据结构和内置函数。像本题中用到的列表、字典( Counter 返回的类似字典的对象)以及列表推导式等都是非常基础且常用的内容,需要熟练掌握它们的用法和特性。
    • 深入学习 collections 库和 heapq 模块。了解 Counter 的各种应用场景,比如统计字符串中字符的出现频率等。对于 heapq,不仅要知道如何构建堆、向堆中插入元素和从堆中弹出元素,还要理解堆的特性(如最小堆的堆顶是最小元素)以及在不同问题中的应用方式。可以通过多做一些类似的练习题,如找出数组中前 k 小的元素(此时可构建最大堆)等,来加深对这些知识点的理解和运用能力。
    • 在遇到算法问题时,要学会分析问题的本质需求,像本题就是要找出前 k 高频率的元素,然后思考有哪些数据结构和算法可以满足这个需求。不要急于动手写代码,先在脑海中构思好大致的解决思路,比如本题先想到统计频率,再想到用堆来筛选,这样可以避免走很多弯路,提高代码的编写效率和质量。