单调队列板子:求滑动窗口中最大值和最小值
文章目录
- 题目分析
- 初始思路
- 单调队列优化的思路
- 代码1:数组模拟单调队列的代码
- 代码2:deque容器实现
能用到单调队列的情景比较有限: 1.典型的有滑动窗口的最值, 2.找到里它最近的比它大(小)的元素。
题目大意:滑动窗口求最值:您的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。
题目分析
初始思路
如果不考虑单调队列优化,普通的做法我们如何思考的呢?
首先是实现窗口的滑动: 维护一个队列,新元素插入到队头,队尾元素删除掉,实现滑动窗口右移一位。 n次操作
其次是求窗口内的最值: 遍历一遍 k次操作
朴素做法总的时间复杂度是O(n×k)O(n\times k)O(n×k)
以上是暴力的做法,也是最容易想到的做法。
单调队列优化的思路
以求最小值为例:该数组为[1 3 -1 -3 5 3 6 7],k为3。
开始时[1 3 -1] 后移一位变成 [ 3 -1 -3] ,如果 -3 是最小值,那么它前面的 -1 和 3就再也用不到了,是多余的,可以删掉。 删掉多余的元素之后, 元素就有了单调性!找滑动窗口内的最小值就是队头元素,时间复杂度为O(1) ,那么这道题单调队列总的时间复杂度就是O(n).
代码1:数组模拟单调队列的代码
思路说完了,上代码和代码解释。
找最小值的思路
一个队列维护当前窗口中的单调上升的情况。当前窗口的最小值就是队头q[tt]。
i表示当前窗口的最右边下标,k表示窗口长度,则窗口最左边下标为i-k+1。即当前滑动窗口 为[队首,队尾]=[ i-k +1 , i].
队列数组q中存放的是点的下标,对于数组模拟的队列q而言,hh表示队头,tt表示队尾,初始化时hh=0,tt=-1,如果hh<=tt表示队列不空。
何时删掉队头元素 ?因为枚举的是右端点,受限的就是左端点。当q队首的值(q中存的是下标)q[hh]小于当前窗口最左边下标的时候:q[hh]<i-k+1此时队头元素删掉,即hh++。
#include<iostream> using namespace std; const int maxn=1e6+10; int a[maxn],q[maxn]; int main(){int n,k;scanf("%d%d",&n,&k);for(int i=0;i<n;i++) scanf("%d",&a[i]);int hh=0,tt=-1;//队头(在左边)和队尾(在右边)for(int i=0;i<n;i++ ){//区间终点是i 那么整个区间是 [ i-k+1,i]//判断队头是否划出窗口,每次查看一次if(hh<=tt&&q[hh]<i-k+1) hh++;//q存的是下标 while(hh<=tt&&a[q[tt]]>=a[i]) tt--;// 队列不空,且新来元素更小,把前面所有冗余(比它大的)都清理掉q[++tt]=i;//把当前最小的放进来//当第一次满足窗口长度k时,往后随着i的递增,每次都要输出最小值,因为窗口在移动if(i>=k-1) printf("%d ",a[q[hh]]);//a【q[]】严格单调上升的最小值,是队头q[hh]}puts("");//输出空格return 0;}同理可找最大值,维护一个队列单调递减,这样队首就是滑动窗口中的最大值。
题目链接:acwing154滑动窗口
ac代码
#include<iostream> using namespace std; const int maxn=1e6+10; int a[maxn],q[maxn]; int main(){int n,k;scanf("%d%d",&n,&k);for(int i=0;i<n;i++) scanf("%d",&a[i]);//找最小值int tt=-1,hh=0; //队头hh,队尾 ttfor(int i=0;i<n;i++){if(hh<=tt&&q[hh]<i-k+1) hh++;while(hh<=tt&&a[q[tt]]>=a[i]) tt--;q[++tt]=i;if(i+1>=k) printf("%d ",a[q[hh]]);}puts("");//找最大值tt=-1,hh=0;for(int i=0;i<n;i++){if(hh<=tt&&q[hh]<i-k+1) hh++;while(hh<=tt&&a[q[tt]]<=a[i]) tt--;//队尾元素小于当前,全部清空q[++tt]=i;if(i+1>=k) printf("%d ",a[q[hh]]);}puts("");return 0;}代码2:deque容器实现
acwing中的ac代码
#include<bits/stdc++.h> using namespace std;const int N=1000010; int a[N]; deque<int> q; int main(){int n,k;cin>>n>>k;for(int i=0;i<n;i++)cin>>a[i];for(int i=0;i<n;i++){if(q.size() && q.front()<i-k+1) q.pop_front();while(q.size() && a[q.back()]>=a[i]) q.pop_back();q.push_back(i);if(i>=k-1) cout<<a[q.front()]<<" ";}cout<<endl;q.clear();for(int i=0;i<n;i++){if(q.size()&& q.front()<i-k+1) q.pop_front();while(q.size()&& a[q.back()]<=a[i]) q.pop_back();q.push_back(i);if(i>=k-1) cout<<a[q.front()]<<" ";}}总结
以上是生活随笔为你收集整理的单调队列板子:求滑动窗口中最大值和最小值的全部内容,希望文章能够帮你解决所遇到的问题。