单调队列(Monotone queue)

2022-10-03 17:23:14
~~首先,我们要知道什么是单调队列~~ ### 单调队列定理 ``` 单调递减(增)的队列 由于单调队列的队头每次一定最小值,故查询时间复杂度O(1) 每个元素最多进(出)队一次,故维护单调队列的时间复杂度仅为O(n) 单调队列入队时:会检查队尾元素是否仍然具备单调性,如果不具备则删除队尾元素,这个对队尾元素“出队”的操作和普通队列是不同的 注:在实际应用中,单调队列直接解题的可能性不大,取而代之的是单调队列基础上改进的斜率优化,虽然使用频率不高,但该结构仍需掌握,因为有许多算法建立在其基础上,且在一些程序中意义非凡 ``` ~~如果连着都不能理解的话,先去上课吧~~ 来玩笑,就算是你看不懂,也可以看看代码 来看看题目 ### 题目描述 ``` 给定一个长度为n(n<=1e6)的数组 有一个大小为k的滑动窗口从数组的最左端移动到最右端 你可以看到窗口中的k个数字 窗口每次向右滑动一个数字的距离 ``` 光是看是理解不了的,让我们来分析一下 从简单的看起,下面是一个例子: 数组是 [1 3 -1 -3 5 3 6 7], k = 3。 | 窗口位置 | 最小值 | 最大值 | | :-----------: | :-----------: | :-----------: | | [1 3 -1] -3 5 3 6 7 | -1 | 3 | | 1 [3 -1 -3] 5 3 6 7 | -3 | 3 | | 1 3 [-1 -3 5] 3 6 7 | -3 | 5 | | 1 3 -1 [-3 5 3] 6 7 | -3 | 5 | | 1 3 -1 -3 [5 3 6] 7 | 3 | 6 | | 1 3 -1 -3 5 [3 6 7] | 3 | 7 | 你的任务是得到滑动窗口在每个位置时的最大值和最小值。 ### 输入 ``` 输入包括两行 第一行包括n和k,分别表示数组的长度和窗口的大小(k<n) 第二行包括n个数字 ``` ### 输出 ``` 输出包括两行 第一行包括窗口从左至右移动的每个位置的最小值 第二行包括窗口从左至右移动的每个位置的最大值 ``` ### 输入样例 ``` 8 3 1 3 -1 -3 5 3 6 7 ``` ### 输出样例 ``` -1 -3 -3 -3 3 3 3 3 5 5 6 7 ``` 首先,我们使用 **数组** 模拟,不涉及任何其他头文件 ``` #include <iostream> using namespace std; ``` 其次,我们使用 **队列储存数组元素下标** ,以判断 **以查看队首是否超时(是否超出移动窗口范围)** ~~终于上代码了~~ ``` #include <iostream> using namespace std; const int N = 1e6+10;// 最多1000000个元素 int q[N],h,t;// 单调队列 队首下标 队尾下标 int a[N],n,k;// 数组 元素个数 窗口下标 int main(){ // 读入 scanf("%d %d",&n,&k); for(int i = 1; i <= n; i++) scanf("%d",&a[i]); // 清空队列 h = 1, t = 0; // 求滑动窗口最小 for(int i = 1; i <= n; i++){ // 队列不为空 队首元素是否超时 if(h <= t && q[h] < i-k+1) h++;// 如果队首元素超时 // 队列不为空 如果当前元素(a[i])<=队尾元素 while(h <= t && a[i] <= a[q[t]]) t--;// 维护单调性 // 元素入队 q[++t] = i; // 如果滑动窗口长度为3 if(i >= k) printf("%d ",a[q[h]]); // 输出 } printf("\n");// 换行 // 清空队列 h = 1, t = 0; // 求滑动窗口最大 for(int i = 1; i <= n; i++){ // 队列不为空 队首元素是否超时 if(h <= t && q[h] < i-k+1) h++;// 如果队首元素超时 // 队列不为空 如果当前元素(a[i])>=队尾元素 while(h <= t && a[i] >= a[q[t]]) t--;// 维护单调性 // 元素入队 q[++t] = i; // 如果滑动窗口长度为3 if(i >= k) printf("%d ",a[q[h]]); // 输出 } printf("\n");// 换行 return 0; } ```