小顶堆(Min-Heap)通常用于实现优先队列。在小顶堆中,根节点的值是最小的,因此通过从堆中移除根节点,你可以高效地获取当前优先级最高(即值最小)的元素。
优先队列的特点:
- 允许高效地插入元素和删除具有最高优先级的元素。
- 可以是基于不同的优先级策略(最小值、最大值等)。
小顶堆的操作:
- 插入操作:O(log n),因为在最坏的情况下,可能需要从底部移动到顶部。
- 删除操作(取出最小值):O(log n),同样需要重建堆结构。
- 获取最小值操作:O(1),只需返回根节点。
#时间复杂度:O(nlogk)
#空间复杂度:O(n)
import heapq
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
#要统计元素出现频率
map_ = {} #nums[i]:对应出现的次数
for i in range(len(nums)):
map_[nums[i]] = map_.get(nums[i], 0) + 1
#对频率排序
#定义一个小顶堆,大小为k
pri_que = [] #小顶堆
#用固定大小为k的小顶堆,扫描所有频率的数值
for key, freq in map_.items():
heapq.heappush(pri_que, (freq, key))
if len(pri_que) > k: #如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
heapq.heappop(pri_que)
#找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
result = [0] * k
for i in range(k-1, -1, -1):
result[i] = heapq.heappop(pri_que)[1]
return result
这段代码的目的是找出一个整数数组中出现频率最高的前K个元素。以下是对代码的详细解释:
时间复杂度与空间复杂度
- 时间复杂度:O(n log k),其中n是数组的长度,k是要找的高频元素的数量。主要的时间开销在于维护小顶堆的操作。
- 空间复杂度:O(n),用于存储元素及其频率的字典。
代码解析
-
导入模块:
import heapq
导入
heapq
模块,用于实现小顶堆。 -
定义类和方法:
class Solution: def topKFrequent(self, nums: List[int], k: int) -> List[int]:
定义一个
Solution
类和一个名为topKFrequent
的方法,接受一个整数列表nums
和一个整数k
。 -
统计频率:
map_ = {} for i in range(len(nums)): map_[nums[i]] = map_.get(nums[i], 0) + 1
使用字典
map_
来存储每个元素及其出现的次数。通过遍历nums
,更新每个元素的频率。 -
小顶堆的初始化:
pri_que = []
初始化一个空的小顶堆
pri_que
。 -
维护小顶堆:
for key, freq in map_.items(): heapq.heappush(pri_que, (freq, key)) if len(pri_que) > k: heapq.heappop(pri_que)
遍历频率字典,将每个元素及其频率作为元组推入小顶堆。如果堆的大小超过k,则弹出频率最低的元素,以确保堆的大小始终为k。
-
提取结果:
result = [0] * k for i in range(k-1, -1, -1): result[i] = heapq.heappop(pri_que)[1] return result
创建一个大小为k的结果数组
result
。然后从小顶堆中依次弹出元素,填充到结果数组中。由于小顶堆是先弹出频率最低的元素,因此需要倒序填充。
总结
这段代码有效地找出了数组中出现频率最高的前K个元素,使用字典统计频率,并利用小顶堆保持高频元素的排序,最终返回结果。
在这个代码片段中,使用了一个固定大小为k的小顶堆来维护频率最高的k个元素。以下是具体的步骤和举例说明:
步骤解析
-
初始化小顶堆:
- 使用
heapq
模块创建一个空的优先队列pri_que
。
- 使用
-
遍历频率映射:
- 对于每个键值对
(key, freq)
,将其作为元组(freq, key)
插入到小顶堆中。
- 对于每个键值对
-
维护堆的大小:
- 每次插入后,检查堆的大小。如果堆的大小超过k,则使用
heapq.heappop(pri_que)
弹出堆顶元素(即频率最低的元素),确保堆的大小始终保持为k。
- 每次插入后,检查堆的大小。如果堆的大小超过k,则使用
举例说明
假设有一个频率映射map_
如下:
map_ = {
'a': 3,
'b': 1,
'c': 2,
'd': 4,
'e': 5
}
并且我们设定k = 3
。
执行过程
- 初始状态:
pri_que = []
-
插入
('a', 3)
:pri_que = [(3, 'a')]
-
插入
('b', 1)
:pri_que = [(1, 'b'), (3, 'a')]
-
插入
('c', 2)
:pri_que = [(1, 'b'), (3, 'a'), (2, 'c')]
- 经过堆调整,变为
pri_que = [(1, 'b'), (3, 'a'),(2, 'c')]
-
插入
('d', 4)
:pri_que = [(1, 'b'), (3, 'a'),(2, 'c'), (4, 'd')]
- 堆大小超过k,弹出堆顶
(1, 'b')
: pri_que = [(2, 'c'),(3, 'a'), (4, 'd')]
-
插入
('e', 5)
:pri_que = [(2, 'c'), (3, 'a'), (4, 'd'), (5, 'e')]
- 堆大小再次超过k,弹出堆顶
(2, 'c')
: pri_que = [(3, 'a'), (4, 'd'), (5, 'e')]
最终结果
经过上述操作后,pri_que
中保留的元素为[(3, 'a'), (4, 'd'), (5, 'e')]
,即频率最高的3个元素是'a'
、'd'
和'e'
。
map_ = {
'a': 3,
'b': 1,
'c': 2,
'd': 4,
'e': 5,
"f":3
}
pri_que = []
for key, freq in map_.items():
heapq.heappush(pri_que, (freq, key))
print(pri_que)
if len(pri_que) > 3: #如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
heapq.heappop(pri_que)
print(pri_que)
好的,如果我们将上述过程的输出结果用中文表述,可以如下说明:
在每次迭代后,优先队列的状态如下:
-
第一次迭代 (key=‘a’, freq=3):
- 当前优先队列:
[(3, 'a')]
- 输出:
[(3, 'a')]
- 当前优先队列:
-
第二次迭代 (key=‘b’, freq=1):
- 向优先队列添加 (1, ‘b’)。
- 当前优先队列:
[(1, 'b'), (3, 'a')]
- 输出:
[(1, 'b'), (3, 'a')]
-
第三次迭代 (key=‘c’, freq=2):
- 向优先队列添加 (2, ‘c’)。
- 当前优先队列:
[(1, 'b'), (2, 'c'), (3, 'a')]
- 输出:
[(1, 'b'), (2, 'c'), (3, 'a')]
-
第四次迭代 (key=‘d’, freq=4):
- 向优先队列添加 (4, ‘d’)。
- 当前优先队列:
[(1, 'b'), (2, 'c'), (3, 'a'), (4, 'd')]
- 由于队列大小超过3,弹出最小的元素。
- 弹出后,当前优先队列:
[(2, 'c'), (3, 'a'), (4, 'd')]
- 输出:
[(1, 'b'), (2, 'c'), (3, 'a'), (4, 'd')] [(2, 'c'), (3, 'a'), (4, 'd')]
-
第五次迭代 (key=‘e’, freq=5):
- 向优先队列添加 (5, ‘e’)。
- 当前优先队列:
[(2, 'c'), (3, 'a'), (4, 'd'), (5, 'e')]
- 弹出最小的元素。
- 弹出后,当前优先队列:
[(3, 'a'), (4, 'd'), (5, 'e')]
- 输出:
[(2, 'c'), (3, 'a'), (4, 'd'), (5, 'e')] [(3, 'a'), (4, 'd'), (5, 'e')]
-
第六次迭代 (key=‘f’, freq=3):
- 向优先队列添加 (3, ‘f’)。
- 当前优先队列:
[(3, 'a'), (3, 'f'), (4, 'd'), (5, 'e')]
- 弹出最小的元素。
- 弹出后,当前优先队列:
[(3, 'f'), (4, 'd'), (5, 'e')]
- 输出:
[(3, 'a'), (3, 'f'), (4, 'd'), (5, 'e')] [(3, 'f'), (4, 'd'), (5, 'e')]
最终输出
综上,经过每次迭代后的输出将是:
在第六次迭代中,优先队列的操作顺序很重要。以下是详细的解释:
- 在第六次迭代中,我们添加了
(3, 'f')
到队列中。 - 此时优先队列中的元素是:
[(3, 'a'), (3, 'f'), (4, 'd'), (5, 'e')]
。 - 因为优先队列是根据频率来排序的,频率相同的情况下,排列顺序可能取决于它们的添加顺序(先进先出)。
- 输入
(3, 'f')
后,队列中有两个元素频率为3
:'a'
和'f'
。根据优先队列的特性,'a'
早于'f'
被添加,因此当我们进行弹出操作时,'(3, 'a')
会被弹出。
所以,在第六次迭代结束后,优先队列的状态应该是 [(3, 'f'), (4, 'd'), (5, 'e')]
。弹出的是 'a'
,而 f
被留下了。
!