前言
**单调队列:**队列内的元素是单调的,递增或者递减。
本题用单调队列存储当前窗口内单调递减的元素。队列从队头到队尾对应窗口内从最大值到尾元素的一个子序列。
单调队列–滑动窗口最大值
传统做法
计算窗口的最大值需要O(m),移动n-m+1次,时间复杂度O(nm)。
【代买演示】
for(int i = m;i<n;i++){
int maxn=a[i];
for(int j=i-m+1;j <= i ;j++){
maxn=max(maxn,a[j]);
}
printf("%d",maxn);
}
优化
- 队头出队:当队头从窗口滑出时,队头元素出队(
head++
);
3. 队尾入队:当新元素滑入队尾时,要把新元素从队尾插入,分两种情况:
1. 直接插入:如果新元素小于队尾元素,那直接从队尾插入(++tail
),因为它可能在前面的最大值滑出窗口后成为最大值。
2. 先删后插:如果新元素大于等于队尾元素,那就先删除队尾元素,因为他不可能成为窗口中最大值,删除方法`tail--`,即从队尾出队。循环删除,直到队空或者遇到一个大于新元素的值,出入其后`(++tail)`。
注意:队列应该存储窗口元素的下标值,便于判断队头出队
步骤 | 过程 | q[h]下标值 | 最大值 |
---|---|---|---|
i=1 | [2 ] 6 4 9 8 5 5 2 | 2 | 窗口不够长 |
i=2 | [2 6] 4 9 8 5 5 2 | 6 | 窗口不够长 |
i=3 | [2 6 4] 9 8 5 5 2 | 6 4 | 6 |
i=4 | 2 [6 4 9] 8 5 5 2 | 9 | 9 |
i=5 | 2 6 [4 9 8] 5 5 2 | 9 8 | 9 |
i=6 | 2 6 4 [9 8 5] 5 2 | 9 8 5 | 9 |
i=7 | 2 6 4 9 [8 5 5] 2 | 8 5 | 8 |
i=8 | 2 6 4 9 8 [5 5 2] | 5 2 | 5 |
【代码演示】
int h = 0,t=-1;
for(int i = 0; i<n; i++){
//如果q[h]不在窗口[i-m+1,i]内,队头出队
if(h<=t && q[h]< i-m+1 )h++;
//当前值>= 队尾值,队尾出队
while(h<=t && a[i] >= a[q[h]])t--;
//下标入队,下标加到队尾
q[++t]=i;
//使用队头最大值,规定窗口长度
if(i>m-1)printf("%d",a[q[h]]);
}
【STL模拟代码演示】
queue<int>q;//双向队列
for(int i = 1;i<=n;i++){
//判断队头是否需要出队
if(!q.empty() && q.front()<i-m+1)q.pop_front();
//维护队列单调性
while(!q.empty() && a[i]>= a[q.back()])
q.pop_back();
//下标入队,便于队头出队
q.push_back(i);
//取队头最为窗口最大值
if(i>= m-1)printf("%d",a[q.front()]);
}
【测试代码】
int h = 0,t=-1;
for(int i = 0; i<n; i++){
printf(" i =%d\n q[%d]=%d q[%d]=%d\n",i,h,q[h],t,q[t]);
//如果q[h]不在窗口[i-m+1,i]内,队头出队
if(h<=t && q[h]< i-m+1 )h++,printf("1 head = %d\n",h);
//当前值>= 队尾值,队尾出队
while(h<=t && a[i] >= a[q[h]])t--;
q[++t]=i,printf(" 2 tail =%d\n",t);
//使用队头最大值
if(i>=m-1)printf("maxn= %d\n",a[q[h]]);
}
【同理求最小值】
for(int i = 0; i<n; i++){
//如果q[h]不在窗口[i-m+1,i]内,队头出队
if(h<=t && q[h]< i-m+1 )h++;
//当前值>= 队尾值,队尾出队
while(h<=t && a[i] <= a[q[t]])t--;//改变最小的下标
//下标入队
q[++t]=i;
//使用队头最大值,规定窗口长度
if(i>= m-1)printf("%d ",a[q[h]]);
}
备注:参考董晓算法思路
总结:如果对你有帮助动动手指一键三连吧!
热情常在,活力无限