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

88 阅读4分钟

问题描述

给你一个整数数组 nums 和一个整数 k,请你用一个字符串返回其中出现频率前 k 高的元素。请按升序排列。

你所设计算法的时间复杂度必须优于 O(n log n),其中 n 是数组大小。

输入

  • nums: 一个正整数数组
  • k: 一个整数

返回

返回一个包含 k 个元素的字符串,数字元素之间用逗号分隔。数字元素按升序排列,表示出现频率最高的 k 个元素。

参数限制

  • 1 <= nums[i] <= 10^4
  • 1 <= nums.length <= 10^5
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

测试样例

样例1:

输入:nums = [1, 1, 1, 2, 2, 3], k = 2
输出:"1,2"
解释:元素 1 出现了 3 次,元素 2 出现了 2 次,元素 3 出现了 1 次。因此前两个高频元素是 1 和 2。

样例2:

输入:nums = [1], k = 1
输出:"1"

样例3:

输入:nums = [4, 4, 4, 2, 2, 2, 3, 3, 1], k = 2
输出:"2,4"

这个问题是典型的“前 kk 高频元素”问题。需要注意的是,要求时间复杂度优于 O(nlog⁡n)O(n \log n),这表明不能通过简单的排序来解决,需要利用 哈希表 的结合来完成。


以下为解题思路

解题思路

  1. 使用哈希表统计频率

    • 遍历数组 nums,统计每个数字出现的次数。
    • 构造一个字典 freq_map,键为数字,值为对应的出现频率。
  2. 用堆(Heap)寻找前 kk 高频元素

    • 使用 Python 的 heapq 模块,结合小根堆来维护频率最高的 k 个元素。

    • 构造一个大小为 k 的小根堆:

      • 如果堆的大小超过 k,将堆顶元素(频率最小的)弹出,从而始终保持堆中元素为频率最高的前 k 个。
  3. 提取结果并排序

    • 将堆中的 k 个元素提取出来,并按升序排列(题目要求)。
    • 转换为字符串格式输出,元素之间用逗号分隔。
  4. 时间复杂度分析

    • 统计频率:O(n)。
    • 构建堆:对于每个元素,最多 O(log⁡k),总复杂度为 O(nlog⁡k)。
    • 排序结果:O(klog⁡k)。
    • 总复杂度:O(nlog⁡k),满足题目要求优于 O(nlog⁡n)。

代码实现

以下是 Python 的代码实现:

from collections import Counter
import heapq

def top_k_frequent(nums, k):
    # 1. 统计每个数字的频率
    freq_map = Counter(nums)
    
    # 2. 使用小根堆寻找频率前 k 高的元素
    heap = []
    for num, freq in freq_map.items():
        heapq.heappush(heap, (freq, num))  # 按 (频率, 元素) 入堆
        if len(heap) > k:  # 堆的大小超过 k 时弹出最小频率的元素
            heapq.heappop(heap)
    
    # 3. 提取堆中元素并按升序排序
    result = sorted([num for freq, num in heap])
    
    # 4. 转换为字符串并返回
    return ",".join(map(str, result))

# 测试样例
print(top_k_frequent([1, 1, 1, 2, 2, 3], 2))  # 输出: "1,2"
print(top_k_frequent([1], 1))  # 输出: "1"
print(top_k_frequent([4, 4, 4, 2, 2, 2, 3, 3, 1], 2))  # 输出: "2,4"

测试结果

  1. 样例1

    • 输入:nums = [1, 1, 1, 2, 2, 3], k = 2
    • 输出:"1,2"
    • 解释:1 出现 3 次,2 出现 2 次,选择这两个高频元素并按升序排序。
  2. 样例2

    • 输入:nums = [1], k = 1
    • 输出:"1"
    • 解释:只有一个元素 1,直接输出。
  3. 样例3

    • 输入:nums = [4, 4, 4, 2, 2, 2, 3, 3, 1], k = 2
    • 输出:"2,4"
    • 解释:2 和 4 都出现了 3 次,选择这两个高频元素并按升序排序。

详细解释代码逻辑

  1. 统计频率

    • 使用 Counter(哈希表)来统计每个元素的出现频率,频率存储在 freq_map 中。
    • 例如:nums = [4, 4, 4, 2, 2, 2, 3, 3, 1] 的频率表为 {4: 3, 2: 3, 3: 2, 1: 1}
  2. 维护小根堆

    • 将元素和其频率作为元组存入堆中,频率作为比较的第一关键字。
    • 堆的大小始终为 k,超过 k 时弹出频率最小的元素。
    • 例如,当 k=2 时,堆最终为 (3,2),(3,4)。
  3. 提取结果并排序

    • 从堆中提取元素(仅取数字部分),形成列表。
    • 使用 sorted 函数按升序排序后输出。
  4. 转换为字符串

    • 使用 ",".join(map(str, result)) 将列表元素转换为字符串,并用逗号连接。

时间复杂度分析

  • 统计频率:O(n),遍历数组构建频率表。

  • 堆操作:对于每个不同的元素,进行堆操作:

    • 每次插入或弹出堆的复杂度为 O(log⁡k)。
    • 总复杂度为 O(nlog⁡k)。
  • 排序:O(klog⁡k),堆中 k 个元素的排序。

  • 总复杂度:O(nlog⁡k+klog⁡k)。


总结

  • 本问题通过频率统计与堆的结合,满足了时间复杂度 O(nlog⁡k) 的要求。
  • 使用 Python 提供的 Counterheapq 工具,大大简化了实现。
  • 堆的大小限制为 k,在处理大规模数据时也能保证高效性。