一、最小堆
- 堆真是个好数据结构,可惜不熟。
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
# 如果窗口大小为1,滑动窗口最大值就是自身
if k == 1: return nums
# 一、错误思路
# firstMax = [nums[0], 0] # 记录窗口中第一大的数值和其下标
# secondmax = [nums[1], 1] # 记录窗口中第二大的数值和其下标
# 可惜对堆不太熟练,想不到用它T^T
nlen = len(nums)
# dp = [0] * (nlen - k + 1)
# # 初始状态
# for i in range(k):
# 二、小根堆/优先队列
# python默认小根堆,即最小的在前面;目的是求最大值,取负值变成最小值,这样就可排在最前面。
pQueue = [(-nums[i], i) for i in range(k)] # 记录数值和下标
# 将列表转换成小根堆
heapq.heapify(pQueue)
res = [-pQueue[0][0]] # 第一个窗口的最大值,小根堆最小值取负就是了
for i in range(k, nlen):
heapq.heappush(pQueue, (-nums[i], i)) # 将新来的数值和下标入小根堆
while pQueue[0][1] <= i - k:
# 如果堆中最小值的下标已经超出k窗口, while直到不越界;
# 因为这个代码不是进一个删一个的逻辑,是一个一个进,等最小值越界才一个一个删除。
heapq.heappop(pQueue) # 删除这个最小值
res.append(-pQueue[0][0]) # 取当前堆中最小值,取负成最大值
return res
二、单调递减的双向队列
- 用递减的队列[其中记录的是下标,以判断是否越界]维持窗口中的最大值max1、第二个最大值max2、第三个最大值…
- 假设在最大值max1下标前有个数值tmp大于第二个最大值max2,但因为窗口往右移动,最大值max1的光芒会掩盖掉tmp,tmp在窗口中,最大值window_max不会是它,tmp不在窗口中,最大值window_max更不会是它。tmp会比max1先消亡,保存tmp没有意义。保存max2的意义就是怕max1的下标在移动窗口后越界了,这时max2顶上去当老大。
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
# 如果窗口大小为1,滑动窗口最大值就是自身
if k == 1: return nums
nlen = len(nums)
# 三、单调的双向队列
# 用递减的队列[其中记录的是下标,以判断是否越界]维持窗口中的最大值、第二个最大值、第三个最大值...,我的思路也类似,但是没想那远,只想到用单调栈。
queue = collections.deque() # 使用包中的双向队列
for i in range(k):
while queue and nums[i] >= nums[queue[-1]]: # 与队列最小的值比较
queue.pop() # 在右边删除小于nums[i]的数,一直while循环删除
queue.append(i) # 添加这个下标;维持递减的队列
# 此时队列中的第一个是这窗口中的最大值的下标
res = [nums[queue[0]]]
for i in range(k, nlen):
while queue and nums[i] >= nums[queue[-1]]:
queue.pop()
queue.append(i) # 维持递减队列
while queue[0] <= i - k:
queue.popleft() # 这里就是要用双向队列的原因,要在左边删除越界了的最大值
res.append(nums[queue[0]])
return res
参考官方解析的代码