今天在豆包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 是数组的大小。
解题思路
- 统计频率:首先,使用
Counter统计每个元素的出现频率。 - 构建最小堆:遍历频率统计结果,将元素和频率插入最小堆中。如果堆的大小超过
k,则弹出堆顶元素(即频率最小的元素)。 - 提取结果:从堆中提取所有元素,按升序排列。
- 格式化输出:将结果格式化为字符串,元素之间用逗号分隔。
以下是代码实现
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的及时解释和反馈,让我彻底理解了这类问题,也解答了内心的困惑,省去了查阅资料的时间,提高了学习效率。