单调队列¶
概述¶
单调队列(Monotonic Queue)是一种特殊的双端队列,队列中的元素始终保持**单调递增**或**单调递减**的性质。它主要用于高效解决**滑动窗口最值问题**,能够在 O(n) 时间复杂度内处理整个数组。
核心特征:单调队列维护窗口内的候选最值元素,通过在入队时移除无效元素,保证队首始终是当前窗口的最值。每个元素最多入队出队一次,因此整体时间复杂度为 O(n)。
与普通队列的对比¶
普通队列 vs 单调队列
普通队列
3
1
4
1
5
↑ 队首
保持入队顺序
特点: FIFO,不保证任何有序性
单调队列(单调递减)
5
4
3
↑ 队首(当前窗口最大值)
保持单调递减顺序
特点: 队首是最大值,队内元素单调递减
单调队列特点¶
1. 双端操作¶
单调队列支持队首和队尾两端操作:
- 队首出队:移除超出窗口范围的元素
- 队尾出队:移除违反单调性的元素
- 队尾入队:新元素从队尾进入(在移除无效元素后)
单调队列操作示意图
单调递减队列(维护窗口最大值)
9
7
5
3
↑ front (最大值)
↑ rear (入队位置)
从队首出队:
• 窗口滑出
• 获取最大值
从队尾入队:
• 移除比新元素小的元素
• 保持单调性
2. 单调性¶
队列中的元素始终保持单调有序:
| 队列类型 | 单调性 | 队首元素 | 应用场景 |
|---|---|---|---|
| 单调递减队列 | 大 → 小 | 最大值 | 滑动窗口最大值 |
| 单调递增队列 | 小 → 大 | 最小值 | 滑动窗口最小值 |
单调递减队列示例:
输入数组: [3, 1, 4, 2, 5, 3]
窗口大小: 3
结论: 队列始终保持单调递减,队首是当前窗口的最大值
3. 下标存储¶
单调队列通常存储**元素下标**而非元素值,原因:
- 判断窗口范围:通过下标计算元素是否超出窗口
- 获取元素值:通过下标在原数组中获取值
- 处理重复元素:下标唯一,避免歧义
| Text Only | |
|---|---|
4. 滑动窗口配合¶
单调队列与滑动窗口完美配合:
原理详解¶
单调队列维护策略¶
以滑动窗口最大值(单调递减队列)为例:
flowchart TB
A[新元素 nums i 入队] --> B{队列是否为空?}
B -->|是| C[直接入队]
B -->|否| D{队尾元素 < 新元素?}
D -->|是| E[移除队尾元素]
E --> D
D -->|否| F[新元素入队]
F --> G{队首下标超出窗口?}
G -->|是| H[移除队首元素]
H --> G
G -->|否| I[队首即为最大值]
C --> I
入队操作详解¶
入队时移除所有比当前元素小的队尾元素(对于单调递减队列):
出队操作详解¶
窗口滑动时移除超出范围的队首元素:
| Text Only | |
|---|---|
完整算法流程¶
可视化演示¶
滑动窗口最大值完整演示¶
| Text Only | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | |
操作流程图¶
sequenceDiagram
participant A as 数组遍历
participant Q as 单调队列
participant R as 结果数组
loop 对于每个元素 i
A->>Q: 检查队首是否超出窗口
Q->>Q: 若超出则移除队首
A->>Q: 检查队尾是否违反单调性
Q->>Q: 若违反则移除队尾
A->>Q: 新元素入队
alt 窗口已满
Q->>R: 记录队首元素值
end
end
代码实现¶
滑动窗口最大值¶
滑动窗口最小值¶
完整结构实现¶
复杂度分析¶
时间复杂度¶
| 操作 | 单次复杂度 | 摊还复杂度 | 说明 |
|---|---|---|---|
| 入队 | O(k) 最坏 | O(1) 摊还 | 最多移除k个元素,但每个元素只被移除一次 |
| 出队 | O(1) | O(1) | 只移除一个元素 |
| 获取最值 | O(1) | O(1) | 直接访问队首 |
整体时间复杂度: O(n)
空间复杂度¶
- O(k):队列最多存储 k 个元素(窗口大小)
单调栈 vs 单调队列¶
| 特性 | 单调栈 | 单调队列 |
|---|---|---|
| 数据结构 | 栈 | 双端队列 |
| 操作端 | 单端(栈顶) | 双端(队首+队尾) |
| 单调性 | 单调递增/递减 | 单调递增/递减 |
| 适用场景 | 下一个更大/更小元素 | 滑动窗口最值 |
| 时间复杂度 | O(n) | O(n) |
| 空间复杂度 | O(n) | O(k) |
应用场景¶
1. 滑动窗口最大值¶
最常见的应用,已在上面详细讲解。
2. 滑动窗口中位数¶
使用两个单调队列维护窗口内的元素:
中位数计算图解:
| Text Only | |
|---|---|
3. 和至少为K的最短子数组¶
使用前缀和 + 单调队列:
算法原理:
| Text Only | |
|---|---|
4. 动态规划优化¶
单调队列可以优化某些动态规划问题:
| Text Only | |
|---|---|
参考资料¶
- 《算法竞赛入门经典》- 单调队列专题
- LeetCode 239. 滑动窗口最大值
- LeetCode 862. 和至少为K的最短子数组
- 单调队列与单调栈的对比与应用