239. 滑动窗口最大值 - 🔗
讲解 - 🔗
💡只想到了要用暴力解法,但是最后超出时间限制了。我觉得最主要的难点在于如何使最大值一直处于队列最左侧,这样子每一次popleft得到的结果就一定是最大值。
方法一:超出时间限制
from collections import deque
class Solution:
def maxSlidingWindow(self, nums: list[int], k: int) -> list[int]:
que = deque()
max_record = float('-inf') # 负无穷
res = []
i = 0
while i < len(nums):
if len(que) < k:
que.append(nums[i])
max_record = nums[i] if max_record < nums[i] else max_record
i += 1
else:
res.append(max_record)
que.popleft() # 移除原k个元素的第一个,模拟向后移的过程
max_record = max(que) if que else float('-inf')
res.append(max_record)
return res
方法二:参考代码
💡每向右移动一位,就相当于popleft一个元素,再append一个元素,然后输出最大值,可以视为一次操作。这个解法就直接把一整个操作包装成了一个类MyQueue
。当MyQueue
下面定义三个方法,其中最重要的是push()
方法。它的主要意图就是确保每输入一次元素,都跟前序元素做比较,确保插入元素一定小雨前序元素,否则就将前序元素移除。这样子就可以确保队列的最左侧一定是当前k个元素中的最大值,那么当下一步进行getMaxVAlue()
的时候就可以直接return self.que[0]
。
from collections import deque
class MyQueue:
def __init__(self):
self.que = deque()
def pop(self, val: int) -> None:
if self.que and self.que[0] == val:
self.que.popleft()
def push(self, val: int) -> None:
while self.que and self.que[-1] < val: # que不为空时,que[-1]才有意义
self.que.pop()
self.que.append(val)
def getMaxValue(self) -> int:
return self.que[0]
class Solution:
def maxSlidingWindow(self, nums: list[int], k: int) -> list[int]:
res = []
que = MyQueue()
# 前k个元素
for i in range(k):
que.push(nums[i])
res.append(que.getMaxValue())
# 剩余元素
for i in range(k, len(nums)):
que.pop(nums[i - k])
que.push(nums[i])
res.append(que.getMaxValue())
return res
347.前 K 个高频元素 - 🔗
讲解 - 🔗
💡之前没有学过堆,思路只停留在用dict存储每个月素出现的次数就卡住了。因为想不到可以用什么来有序地遍历或者存储dict中的元素和值。看了解析之后,才发现堆这种数据结构的优势,正好可以用于解决此类问题。因为堆在插入元素时会自动维护堆的性质,确保堆仍然保持有序。具体来说,对于小顶堆(最小堆),插入新元素后,堆会自动进行调整,将新元素放到合适的位置,以满足小顶堆的性质,即每个节点都小于或等于其子节点。
一、堆介绍:
在计算机科学中,堆(Heap)是一种特殊的数据结构,它是一种树形结构,通常用于实现优先队列。堆具有以下特性:
- 完全二叉树: 堆通常是一个完全二叉树,这意味着除了最后一层,其他所有层的节点都被完全填充,而且最后一层的节点都尽可能靠左排列。
- 堆属性: 在最小堆中,父节点的值小于或等于其子节点的值;在最大堆中,父节点的值大于或等于其子节点的值。这就是所谓的"堆属性"。
二、用python实现堆:
在 Python 中,堆通常通过 heapq
模块来实现。heapq
提供了一些堆操作的函数,用于在列表上实现堆的行为。以下是一些常见的堆操作:
1. 创建堆:
heapq.heapify(iterable): 将可迭代对象(如列表)转换为堆。
2. 堆的基本操作:
heapq.heappush(heap, elem): 将元素添加到堆中。
heapq.heappop(heap): 弹出并返回堆中的最小元素。
3. 获取堆的最小元素(不弹出):
heapq.nsmallest(n, iterable): 返回可迭代对象中的前 n 个最小元素。
heapq.nlargest(n, iterable): 返回可迭代对象中的前 n 个最大元素。
4. 合并堆:
heapq.merge(*iterables): 合并多个有序序列,返回一个排序后的新序列。
5. 堆排序:
heapq.heapsort(iterable): 在-place 对可迭代对象进行堆排序。
6. 获取堆的最小值(不弹出):
heapq.peek(heap): 返回堆中的最小元素,但不弹出。
import heapq
# 创建一个空堆
heap = []
# 将元素加入堆
heapq.heappush(heap, 4)
heapq.heappush(heap, 1)
heapq.heappush(heap, 7)
# 弹出堆中最小元素
min_element = heapq.heappop(heap)
# 获取堆中最小元素,但不弹出
min_element = heap[0]
# 将列表转换为堆
heapq.heapify(heap)
# 从堆中删除指定元素
heapq.heappop(heap)
# 合并多个有序序列,返回一个排序后的新序列
merged = heapq.merge([1, 3, 5], [2, 4, 6])
请注意,
heapq
默认实现的是最小堆。如果需要最大堆,可以通过将元素取负数的方式实现。
# 最大堆
max_heap = [-x for x in heap]
#时间复杂度:O(nlogk)
#空间复杂度:O(n)
import heapq
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
# 用字典要统计元素出现频率
map_ = {}
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(): # key为元素值,frep为元素出现频率
heapq.heappush(pri_que, (freq, key)) # 插入(freq, key)元素,自动比较freq的大小
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
值得注意的是heapq.heappush(pri_que, (freq, key))
,在heapq.haedpush()
函数的第二个参数中传入了一个元组(用()
表示)。在小顶堆中,元组的比较是从左到右按照元组元素的顺序进行的。因此,由于这道题目要比较的是频率的大小,而非元素值的大小,所以把freq放在key前面,即(freq, key)
,而非(key, freq)
。