题目链接:https://leetcode.cn/problems/sliding-window-maximum/description/
1.问题理解
场景有一个数组
nums和一个窗口大小k,窗口从数组左边滑到右边,每次滑动一格。你需要记录每次窗口停下时,窗口内的最大值。示例:
nums = [1,3,-1,-3,5,3,6,7],k=3
第一个窗口[1,3,-1]的最大值是3,第二个窗口[3,-1,-3]的最大值还是3……最后结果是[3,3,5,5,6,7]
2.核心算法:单调队列
为什么用单调队列?
暴力解法重复计算了窗口中的值,而单调队列通过维护一个递减的队列,保证队头永远是当前窗口的最大值,避免重复比较
算法步骤:
1.初始化:一个双端队列kept_nums,结果立碑max_list
2.遍历数组:
新元素nums[i]入队时,从队尾弹出所有比它小的数(之前的数),而之前加入进来又比它小的数不可能是后续窗口的最大值
将nums[i]加入队尾(从队尾加入(右->左))
如果队头元素是原窗口左侧刚滑过去的元素,则弹出队头(比如原窗口从 [5,3,1] 右移到 [3,1,4] 时,原左侧的 5 需要被移除。)
当窗口完全形成(假如k=3),队头就是当前窗口最大值(比如原窗口[5,3,1] 完全形成后,队头就是 5,5 就是最大值)
3.关键点和边界条件
关键点:
单调性:队列必须严格单调递减,队头必须是最大值
过期处理:队头元素可能因原窗口滑动失效,需要及时移除
边界条件:
空数组或者k=1(每个窗口只有 1 个数),直接返回原数组
k > len(nums):返回该窗口的最大值
队列清理顺序:先清理队尾,再处理过期队头,最后记录结果
from collections import deque
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
max_list = [] #结果集
kept_nums = deque() #单调队列
for i in range(len(nums)):
update_kept_nums(kept_nums,nums[i]) #nums新加入的元素
if i >=k and nums[i-k] == kept_nums[0]: #左侧旧元素如果等于单调队列头元素,需要移除头元素
kept_nums.popleft()
if i >= k - 1: #因为 nums 索引是从 0 开始,这里要-1
max_list.append(kept_nums[0])
return max_list
def update_kept_nums(kept_nums,num):
#如果队列里面不为空 并且 新加入的元素大于之前的元素
while kept_nums and num > kept_nums[-1]:
kept_nums.pop()
kept_nums.append(num)