2025/3/10 【栈与队列】LeetCode347.前K个高频元素 【🌟】知识点:import heapq ,堆队列(优先队列)
我的方法:
1.遍历nums,用字典统计频率,O(n)
2.用sort根据字典中键对应的值对键进行排序,O(n log n)
3.取排序的键列表的前K个元素,O(k)
总的时间复杂度为 O(n) + O(n log n) + O(k)。由于 O(n log n) 是主导项,所以总的时间复杂度为 O(n log n)
from collections import defaultdict class Solution: def topKFrequent(self, nums: List[int], k: int) -> List[int]: mydict = defaultdict(int) for num in nums: mydict[num] += 1 mydict_key = sorted(mydict, key=mydict.get, reverse=True) return mydict_key[:k]
这个方法比代码随想录提供的Python的解法二好理解,更加明了。
为什么不是 sorted(mydict.keys(), key=mydict.get, reverse=True)?
sorted(mydict, key=mydict.get, reverse=True) 和 sorted(mydict.keys(), key=mydict.get, reverse=True) 的功能是 完全相同的,它们的返回结果也是一样的。区别在于前者是直接对字典 mydict 进行迭代,而后者是显式地调用 mydict.keys() 获取键后再进行排序。
为什么两者等价?
-
字典的默认迭代行为:
-
在 Python 中,直接对字典进行迭代(如
for key in mydict)会默认迭代字典的 键(keys)。 -
因此,
sorted(mydict, ...)实际上是对字典的键进行排序。
-
-
mydict.keys():-
mydict.keys()显式地返回字典的键视图(dict_keys对象),它是一个可迭代对象,包含字典的所有键。 -
因此,
sorted(mydict.keys(), ...)也是对字典的键进行排序。
-
推荐直接使用 sorted(mydict, ...)
-
sorted(mydict, ...)更简洁,不需要显式调用.keys()。 -
Python 的设计哲学之一是简洁明了,直接对字典进行迭代是更常见的写法。
- 直接对字典进行迭代是Python中常见的习惯用法,代码更易读且符合Python的风格。
额外说明
如果你需要对字典的 键值对(key-value pairs) 进行排序,可以使用 mydict.items(),例如:
sorted(mydict.items(), key=lambda item: item[1], reverse=True)
这会返回一个列表,列表中的元素是 (key, value) 元组,按值从大到小排序。例如:
[('a', 3), ('c', 2), ('b', 1)]
方法2:小顶堆
代码使用了最小堆来高效地维护频率最高的 k 个元素,适合 k 远小于 n 的场景。
实现1:时间复杂度是 O(n log k)
import heapq class Solution: def topKFrequent(self, nums: List[int], k: int) -> List[int]: mydict = {} for num in nums: mydict[num] = mydict.get(num, 0) + 1 return heapq.nlargest(k, mydict.keys(), key=mydict.get)
1. 为什么不是 heapq.nlargest(k, mydict.keys(), key=mydict.get())?
-
mydict.get():-
mydict.get是一个函数,而mydict.get()是调用这个函数。 -
调用
mydict.get()时,如果没有提供默认值,会抛出TypeError,因为get方法需要一个键作为参数。 -
即使提供了默认值(例如
mydict.get(default=0)),heapq.nlargest的key参数需要的是一个函数,而不是函数的返回值。
-
-
错误原因:
-
key参数需要的是一个函数,而mydict.get()是一个返回值,不符合要求。
-
2. 为什么不是 heapq.nlargest(k, mydict, key=mydict.get)?
-
mydict:-
直接传入
mydict时,heapq.nlargest会遍历字典的键(keys),而不是键值对(items)。 -
例如,如果
mydict = {'a': 3, 'b': 1, 'c': 2},heapq.nlargest会遍历'a'、'b'、'c'。
-
-
key=mydict.get:-
mydict.get会根据键返回对应的值。 -
例如:
-
mydict.get('a')返回3。 -
mydict.get('b')返回1。 -
mydict.get('c')返回2。
-
-
-
结果:
-
这种写法与
heapq.nlargest(k, mydict.keys(), key=mydict.get)是等价的,因为heapq.nlargest会默认遍历字典的键。 -
但是,直接传入
mydict的写法不够直观,容易引起误解,因此推荐使用mydict.keys()明确表示遍历的是键。
-
推荐写法
推荐使用以下写法,既直观又明确:
heapq.nlargest(k, mydict.keys(), key=mydict.get)
或者更简洁地使用字典的 items() 方法:
heapq.nlargest(k, mydict.items(), key=lambda x: x[1])
-
这种写法直接遍历键值对,排序依据是值(
x[1]),返回的结果是键值对的列表。
实现2:总的时间复杂度为 O(n log k)
import heapq class Solution: def topKFrequent(self, nums: List[int], k: int) -> List[int]: # 统计频率:O(n) mydict = {} for num in nums: mydict[num] = mydict.get(num, 0) + 1 # 堆操作:每次 heapq.heappush 和 heapq.heappop 的时间复杂度为 O(log k), # 总共有 n 次操作,时间复杂度为 O(n log k) s_heapq = [] for num, freq in mydict.items(): # heapq 默认根据元组的第一个元素(即 num)进行排序 heapq.heappush(s_heapq, (freq, num)) if len(s_heapq) > k: heapq.heappop(s_heapq) # 构建结果数组:O(k log k) res = [] for i in range(k): res.append(heapq.heappop(s_heapq)[1]) return res\
知识点:
`heapq` 是 Python 标准库中的一个模块,提供了堆队列算法的实现,也称为 优先队列。堆是一种特殊的二叉树结构,满足以下性质:
- 在最小堆中,父节点的值总是小于或等于其子节点的值。
- 在最大堆中,父节点的值总是大于或等于其子节点的值。
`heapq` 模块默认实现的是 **最小堆**,即堆顶元素是最小的。通过堆,可以高效地实现插入、删除和获取最小(或最大)元素的操作。
---
1. `heapq` 模块的基本功能
(1)
heapq.heappush(heap, item)
- 功能:将元素 `item` 插入堆 `heap` 中,并保持堆的性质。
- 时间复杂度:`O(log n)`,其中 `n` 是堆的大小。
(2)
heapq.heappop(heap)
- 功能:从堆 `heap` 中弹出并返回最小的元素(堆顶元素),并保持堆的性质。
- 时间复杂度:`O(log n)`,其中 `n` 是堆的大小。
(3)
heapq.nlargest(k, iterable, key=None)
- 功能:从可迭代对象 `iterable` 中返回最大的 `k` 个元素。
- 参数:
- `k`:需要返回的元素个数。
- `iterable`:可迭代对象(如列表、字典等)。
- `key`:可选参数,用于指定排序的依据(类似于 `sorted` 的 `key` 参数)。
- 时间复杂度:`O(n log k)`,其中 `n` 是可迭代对象的大小。
(4)
heapq.nsmallest(k, iterable, key=None)
- 功能:从可迭代对象 `iterable` 中返回最小的 `k` 个元素。
- 参数:与 `heapq.nlargest` 相同。
- 时间复杂度:`O(n log k)`,其中 `n` 是可迭代对象的大小。
2. 堆的性质
- 最小堆:堆顶元素是最小的。
- 最大堆:可以通过将元素取反来实现。例如,将 `[3, 1, 2]` 存储为 `[-3, -1, -2]`,这样堆顶元素是 `-3`,取反后就是最大的元素。
---
3. 使用 `heapq` 实现最大堆
由于 `heapq` 默认是最小堆,可以通过将元素取反来实现最大堆:
import heapq heap = [] heapq.heappush(heap, -3) heapq.heappush(heap, -1) heapq.heappush(heap, -2) print(heap) # 输出: [-3, -1, -2] largest = -heapq.heappop(heap) print(largest) # 输出: 3
---
4. 使用 `heapq` 的典型场景
(1) **Top K 问题**
- 使用 `heapq.nlargest` 或 `heapq.nsmallest` 可以高效地解决 Top K 问题。
- 示例:
import heapq nums = [3, 1, 4, 1, 5, 9, 2, 6] top_3 = heapq.nlargest(3, nums) print(top_3) # 输出: [9, 6, 5]
(2) 合并有序列表
- 使用 `heapq.merge` 可以高效地合并多个有序列表。
- 示例:
import heapq list1 = [1, 3, 5] list2 = [2, 4, 6] merged = list(heapq.merge(list1, list2)) print(merged) # 输出: [1, 2, 3, 4, 5, 6]
(3) 优先队列
- 使用 `heapq` 可以实现优先队列,优先级高的元素先出队。
- 示例:
import heapq heap = [] heapq.heappush(heap, (2, 'code')) heapq.heappush(heap, (1, 'eat')) heapq.heappush(heap, (3, 'sleep')) while heap: priority, task = heapq.heappop(heap) print(task) # 输出: eat, code, sleep
---
优先队列(Priority Queue) 是一种抽象数据类型,它类似于普通的队列(Queue),但每个元素都有一个优先级(Priority)。
在优先队列中,元素按照优先级顺序出队,而不是按照入队的顺序。优先级高的元素会先出队。
---
1. 优先队列的特点
- 优先级决定出队顺序:
- 在普通队列中,元素遵循“先进先出”(FIFO)的原则。
- 在优先队列中,元素遵循“优先级高者先出”的原则。
- 动态插入和删除:
- 优先队列支持动态插入元素和删除优先级最高的元素。
- 实现方式:
- 优先队列通常通过 堆(Heap) 来实现,因为堆可以高效地维护元素的优先级顺序。
---
2. 优先队列的操作
优先队列通常支持以下基本操作:
(1) 插入(Insert / Push)
- 将一个元素插入优先队列中。
- 时间复杂度:`O(log n)`,其中 `n` 是队列中的元素个数。
(2) 删除最高优先级元素(Delete-Max / Delete-Min / Pop)
- 删除并返回优先队列中优先级最高(或最低)的元素。
- 时间复杂度:`O(log n)`。
(3)查看最高优先级元素(Peek-Max / Peek-Min)
- 返回优先队列中优先级最高(或最低)的元素,但不删除它。
- 时间复杂度:`O(1)`。
(4) 判断是否为空(Is-Empty)
- 检查优先队列是否为空。
- 时间复杂度:`O(1)`。
---
3. 优先队列的实现方式
优先队列可以通过多种数据结构实现,常见的实现方式包括:
(1) **堆(Heap)**
- **最小堆**:优先级最低的元素在堆顶。
- **最大堆**:优先级最高的元素在堆顶。
- 时间复杂度:
- 插入:`O(log n)`。
- 删除:`O(log n)`。
- 查看堆顶元素:`O(1)`。
- 优点:高效且易于实现。
(2) 平衡二叉搜索树(Balanced Binary Search Tree)
(3) 无序数组或链表
总结:我靠,花了好多时间总算吃透这题!
浙公网安备 33010602011771号