青训营X豆包MarsCode 查找热点数据问题思路及解析| 豆包MarsCode AI 刷题

46 阅读4分钟

思路分析

  1. 统计频率: 在许多编程问题中,统计元素的出现频率是解决问题的第一步。我们可以使用 Python 中的 collections.Counter 来非常方便地统计数组或其他序列中每个元素的频率。Counter 是一个字典的子类,其键值对分别表示元素和其出现的次数。通过这种方式,我们可以快速获得每个元素的出现频率,从而为后续处理打下基础。

    例如,如果我们有一个包含数字的列表 nums = [1, 1, 1, 2, 2, 3],我们可以通过 Counter(nums) 得到频率映射 Counter({1: 3, 2: 2, 3: 1})。这一步的时间复杂度为 O(n),其中 n 是数组的长度。

  2. 构建频率映射: 使用 Counter 类得到的频率映射本质上是一个字典,键为数组元素,值为对应的频率。例如,在 Counter({1: 3, 2: 2, 3: 1}) 中,数字 1 出现了 3 次,数字 2 出现了 2 次,数字 3 出现了 1 次。频率映射为我们后续选择频率最高的元素提供了基础数据结构。

    对于频率映射的构建,时间复杂度为 O(n),因为我们只需要遍历一次输入数组来统计每个元素的出现次数。

  3. 使用堆选择高频元素: 要找出频率最高的 k 个元素,我们使用最小堆(Min-Heap)来实现。最小堆是一种二叉堆,它始终保证堆顶元素是堆中的最小元素。在这个问题中,我们将每个元素的频率作为堆的优先级,并使用堆来维护频率最高的 k 个元素。

    具体来说,堆的大小始终保持为 k,我们遍历频率字典,对于每一个元素,如果堆的大小小于 k,我们将元素加入堆中;如果堆的大小已经是 k,并且当前元素的频率大于堆顶元素的频率,我们就弹出堆顶元素,并将当前元素插入堆中。通过这种方式,堆顶始终保存着频率最小的元素,确保堆中的 k 个元素是频率最高的。

    heapq 模块是 Python 提供的一个堆队列算法库,其中的 heappush 用于将元素加入堆,heappop 用于从堆中弹出最小元素。在最坏情况下,插入和删除操作的时间复杂度是 O(log k),因此我们对每一个元素执行的堆操作的总时间复杂度为 O(n log k),其中 n 是数组的长度,k 是我们需要找出的元素个数。

  4. 排序输出: 在得到频率最高的 k 个元素之后,我们还需要按升序对这些元素进行排序。因为题目要求返回的元素是升序排列的,所以我们可以直接使用 Python 内置的 sort() 方法对结果进行排序。排序的时间复杂度为 O(k log k),因为最终结果的大小是 k,最多对 k 个元素进行排序。

  5. 时间复杂度分析

    • 统计频率的时间复杂度是 O(n),其中 n 是数组的长度。
    • 向堆中插入元素的时间复杂度是 O(log k),最多插入 n 个元素,所以所有插入操作的总时间复杂度是 O(n log k)。
    • 最后的排序操作的时间复杂度是 O(k log k),因为最多有 k 个元素需要排序。
    • 综合起来,整体的时间复杂度为 O(n log k),其中 n 是输入数组的长度,k 是我们要找出的频率最高的元素个数。
  6. 空间复杂度分析

    • 空间复杂度主要来自于存储频率映射(Counter 返回的字典)和堆。频率映射需要 O(n) 的空间来存储每个元素的频率,堆需要 O(k) 的空间来存储 k 个频率最高的元素。因此,总的空间复杂度为 O(n + k)。

思路流程图

erDiagram
    START ||--o{ COUNTER : starts
    COUNTER ||--o{ CREATE_HEAP : creates
    CREATE_HEAP ||--o{ ITERATE_FREQUENCY : iterates
    ITERATE_FREQUENCY ||--o{ INSERT_TO_HEAP : inserts
    INSERT_TO_HEAP ||--o{ CHECK_HEAP_SIZE : checks
    CHECK_HEAP_SIZE ||--o{ REMOVE_MIN_ELEMENT : removes
    CHECK_HEAP_SIZE ||--o{ INSERT_NEXT_ELEMENT : inserts_next
    REMOVE_MIN_ELEMENT ||--o{ INSERT_NEXT_ELEMENT : continues
    INSERT_NEXT_ELEMENT ||--o{ EXTRACT_TOP_K : extracts
    EXTRACT_TOP_K ||--o{ SORT_RESULT : sorts
    SORT_RESULT ||--o{ CONVERT_TO_STRING : converts
    CONVERT_TO_STRING ||--o{ RETURN_RESULT : returns
    RETURN_RESULT ||--o{ END : ends

代码

import heapq
from collections import Counter

def solution(nums, k):
    # 统计每个元素的频率
    freq_map = Counter(nums)
    
    # 使用最小堆来保存频率最高的k个元素
    heap = []
    
    # 遍历频率字典,将每个元素和频率放入堆中
    for num, freq in freq_map.items():
        heapq.heappush(heap, (freq, num))
        # 如果堆的大小超过k,移除堆中频率最小的元素
        if len(heap) > k:
            heapq.heappop(heap)
    
    # 从堆中取出前k个频率最大的元素
    result = [num for _, num in heap]
    
    # 按元素值升序排列
    result.sort()
    
    # 将结果转换为字符串,并用逗号连接
    return ",".join(map(str, result))

if __name__ == "__main__":
    # 测试用例
    print(solution([1, 1, 1, 2, 2, 3], 2))  # 输出:"1,2"
    print(solution([1], 1))  # 输出:"1"
    print(solution([4, 4, 4, 2, 2, 2, 3, 3, 1], 2))  # 输出:"2,4"