一、前置知识点:单调队列与滑动窗口
1. 滑动窗口(Sliding Window)
- 定义:在数组 / 字符串上维护一个固定长度的 “窗口”,窗口随索引向右滑动,用于高效处理区间问题。
- 核心痛点:若每次滑动后都遍历窗口求最大值,时间复杂度为 O (nk),数据量大时会超时。
2. 单调队列(Monotonic Queue)
-
定义:一种特殊队列,内部元素始终保持单调递增 / 递减顺序。
-
核心优势:
- 队首元素始终是当前窗口内的最大值(或最小值),O (1) 获取极值。
- 入队时移除队尾所有比当前元素小的元素,保证队列单调性。
- 队首元素超出窗口范围时,自动出队。
-
实现:使用 Python
collections.deque实现双端队列,支持 O (1) 时间的两端操作。
二、经典算法题:滑动窗口最大值(LeetCode 239)
题目描述
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到最右侧。你只能看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
示例:
- 输入:
nums = [1, 3, -1, -3, 5, 3, 6, 7],k = 3 - 输出:
[3, 3, 5, 5, 6, 7]
最优解法:单调双端队列
核心思路
- 队列存储索引:队列中保存的是元素索引,而非值,方便判断是否超出窗口范围。
- 维护单调性:新元素入队前,移除队尾所有比它小的元素,保证队列从大到小排列。
- 移除过期元素:若队首索引超出当前窗口左边界,将其从队首弹出。
- 记录结果:当窗口形成(索引
i >= k-1)时,队首元素即为当前窗口最大值。
代码实现
from typing import List
from collections import deque
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
q = deque() # 双端队列,存储索引,单调递减
res = []
for i in range(len(nums)):
# 窗口左边界
left = i - k + 1
# 1. 维护单调递减:移除队尾所有 < 当前元素的索引
while q and nums[q[-1]] < nums[i]:
q.pop()
# 当前索引入队
q.append(i)
# 2. 移除超出窗口左边界的队首元素
if q[0] < left:
q.popleft()
# 3. 窗口形成后,记录当前窗口最大值(队首)
if i >= k - 1:
res.append(nums[q[0]])
return res
代码执行示例
以 nums = [1, 3, -1, -3, 5, 3, 6, 7],k = 3 为例:
i=0:队列[0],窗口未形成i=1:移除0,队列[1],窗口未形成i=2:队列[1, 2],窗口形成 → 记录nums[1]=3i=3:队列[1, 2, 3],窗口形成 → 记录nums[1]=3i=4:移除3, 2, 1,队列[4],窗口形成 → 记录nums[4]=5- 后续依次得到
[5, 6, 7],最终结果[3, 3, 5, 5, 6, 7]
三、关键知识点总结
1. 复杂度分析
- 时间复杂度:O (n),每个元素最多入队和出队各一次。
- 空间复杂度:O (k),队列最多存储 k 个元素。
2. 单调队列核心操作
| 操作 | 作用 |
|---|---|
q.append(i) | 当前元素索引入队 |
q.pop() | 移除队尾,维护单调性 |
q.popleft() | 移除队首,清理超出窗口的元素 |
nums[q[0]] | O (1) 获取当前窗口最大值 |
3. 适用场景拓展
单调队列 + 滑动窗口适用于:
- 滑动窗口极值:本题、滑动窗口最小值
- 单调栈 / 队列扩展:如柱状图中最大矩形、最大矩形面积等区间极值问题