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

90 阅读4分钟

今天在豆包MarsCode 刷到了这样一道题

问题描述

给你一个整数数组 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 个高频元素的集合是唯一的

AI及时的告诉我这属于优先队列(Priority Queue)堆(Heap) 类型的题目。具体来说,它涉及到使用最小堆来高效地查找前 k 个高频元素。

题目类型

  • 优先队列:我们需要维护一个优先队列来高效地找到前 k 个高频元素。
  • :使用最小堆(min-heap)来维护前 k 个高频元素,可以在 O(log k) 时间内插入和删除元素。

问题理解

我们需要找到数组 nums 中出现频率最高的 k 个元素,并按升序排列这些元素。时间复杂度要求优于 O(n log n),其中 n 是数组的大小。

解题思路

  1. 统计频率:首先,使用 Counter 统计每个元素的出现频率。
  2. 构建最小堆:遍历频率统计结果,将元素和频率插入最小堆中。如果堆的大小超过 k,则弹出堆顶元素(即频率最小的元素)。
  3. 提取结果:从堆中提取所有元素,按升序排列。
  4. 格式化输出:将结果格式化为字符串,元素之间用逗号分隔。

以下是代码实现

from collections import Counter

import heapq

def solution(nums, k):

# Please write your code here

# 统计频率

freq_map = Counter(nums)

# 使用最小堆维护前 k 个高频元素

min_heap = []

for num, freq in freq_map.items():

# 将 (频率, 元素) 插入最小堆

heapq.heappush(min_heap, (freq, num))

# 如果堆的大小超过 k,弹出堆顶元素

if len(min_heap) > k:

heapq.heappop(min_heap)

# 从堆中提取所有元素,按升序排列

result = [num for _, num in min_heap]

# 格式化输出

return ','.join(map(str, sorted(result)))

但是在这个问题当中,你是否会有这样的困惑,就是为什么我们使用最小堆而不是最大堆?对于这个问题,AI也是给出了及时的反馈。 使用最小堆而不是最大堆的原因在于我们需要维护前 k 个高频元素,并且最终结果需要按升序排列。最小堆在这里有几个优势:

1. 维护前 k 个高频元素

最小堆可以帮助我们在 O(log k) 时间内维护 k 个最大元素。具体来说:

  • 插入元素:每次插入一个元素到最小堆中,时间复杂度是 O(log k)
  • 弹出最小元素:如果堆的大小超过 k,我们弹出堆顶元素(即频率最小的元素),时间复杂度也是 O(log k)

2. 最终结果按升序排列

最小堆的堆顶元素是频率最小的元素。当我们需要按升序排列结果时,可以直接从堆中提取元素,而不需要再次排序。

如果使用最大堆,我们需要维护 k个最小元素,这会导致以下问题:

  • 插入元素:每次插入一个元素到最大堆中,时间复杂度是 O(log k)
  • 弹出最大元素:如果堆的大小超过 k,我们弹出堆顶元素(即频率最大的元素),时间复杂度也是 O(log k)
  • 最终结果排序:从最大堆中提取元素后,我们还需要对结果进行排序,这会增加额外的 O(k log k) 时间复杂度。

因此,使用最小堆可以在 O(n log k) 时间内完成任务,并且最终结果可以直接按升序排列,而不需要额外的排序操作。

通过AI的及时解释和反馈,让我彻底理解了这类问题,也解答了内心的困惑,省去了查阅资料的时间,提高了学习效率。