每日c/c++题 备战蓝桥杯(洛谷P1440 求m区间内的最小值 详解(单调队列优化))
洛谷P1440 求m区间内的最小值 详解(单调队列优化)
题目描述
给定一个长度为n的序列,要求输出每个长度为m的连续子数组的最小值。若m>n则输出全0。
算法思路
暴力解法缺陷:
直接遍历每个窗口并遍历求最小值,时间复杂度为O(nm),当n=2e6时会超时。
单调队列优化:
使用双端队列维护一个单调递增序列,队列中存储元素的下标,保证:
- 队列头部为当前窗口的最小值下标
- 队列中元素对应的值严格递增
- 当窗口滑动时,移除失效元素(超出窗口范围的下标)
代码详解(附修正与优化)
#include <bits/stdc++.h>
using namespace std;const int MAXN = 2e6 + 5;
int n, m;
int a[MAXN]; // 存储原始数组
int que[MAXN]; // 单调队列(存储下标)
int front = 0, rear = -1; // 队列指针int main() {ios::sync_with_stdio(false);cin >> n >> m;// 处理m>n的特殊情况if (m > n) {for (int i = 0; i < n; ++i) cout << "0\n";return 0;}for (int i = 1; i <= n; ++i) cin >> a[i];// 初始化队列(前m个元素)for (int i = 1; i < m; ++i) {while (rear >= front && a[que[rear]] >= a[i]) rear--;que[++rear] = i;}// 输出第一个窗口的最小值cout << a[que[front]] << "\n";// 滑动窗口处理后续元素for (int i = m; i <= n; ++i) {// 移除失效队首(超出窗口范围)while (rear >= front && que[front] <= i - m) front++;// 维护单调性(从队尾移除较大元素)while (rear >= front && a[que[rear]] >= a[i]) rear--;que[++rear] = i;// 输出当前窗口最小值cout << a[que[front]] << "\n";}return 0;
}
关键优化点说明
-
边界条件处理:
- 当m>n时直接输出全0(题目特殊要求)
- 初始化时先处理前m-1个元素,避免初始窗口未形成时的越界访问
-
时间复杂度O(n):
每个元素最多入队、出队各一次,总操作次数为O(n) -
空间复杂度O(n):
使用线性数组模拟双端队列,避免STL容器额外开销
算法执行流程示例
以输入 n=5, m=3
,数组 [1,3,2,4,5]
为例:
- 初始化窗口[1,3,2]:队列存[1,3],最小值1
- 滑动到[3,2,4]:队列存[2,4],最小值2
- 滑动到[2,4,5]:队列存[2,4,5],最小值2
最终输出:
1
2
2
注意事项
- 数组下标从1开始:避免处理0号元素的特殊情况
- 严格维护单调性:当新元素小于等于队尾元素时,必须弹出队尾
- 窗口有效性检查:每次滑动后需确保队首元素在窗口范围内
总结
本题通过单调队列将时间复杂度从O(nm)优化至O(n),核心思想是:
- 维护一个随时可获取最小值的"滑动窗口"
- 通过队列的单调性保证最小值始终在队首
- 及时清理失效元素(超出窗口范围或被更大元素覆盖)
此算法是滑动窗口类问题的经典解法,可推广至求最大值、中位数等变种问题。