单调队列
算法思想:
-
先考虑用普通队列来做该如何做,即用一个队列维护窗口,用入队出队模拟窗口移动O(n),线性扫描队列求最值O(k)
-
当前队列里右面的数比左面的数小(-1,-3),而且左面的数会在右面的数前面出队列,换句话说,只要-1还在,-3一定在,所以最小值一定不会取到-1,所以-1就可以删掉了。即如果后一位的元素比当前的元素小,当前这个元素就没有意义了,删掉。这样把当前队列里面的冗余元素都删掉之后,剩下的元素是递增的。这样求最小值的时候就可以把O(k)优化到O(1),因为只要取队头就好了。求最大值对称地做就可以了。
-
总结以上步骤和分析过程:1.用普通队列该怎么做
2.将队列中的没有用的元素删掉----->具有了单调性(当前窗口)
3.可以用O(1)时间从队头/队尾取出最值
- 使用场景:滑动窗口的最值
- 实现的时候一般手写双端队列(数组模拟)
代码实现:
//例子:滑动窗口
//手写双端队列
//队列里存的是数字在原数组中的下标
int a[N],q[N];
int main()
{
int n,k;
cin>>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++)
{
//判断当前的队头已经不在当前的窗口中
if(hh<=tt&&q[hh]<i-k+1) hh++;//辐射不到
while(hh<=tt&&a[q[tt]]>=a[i]) tt--;
//当前的数加进来
q[++tt] = i;
//保证至少有一个窗口
if(i>=k-1) printf("%d ",a[q[hh]]);
}
cout<<endl;
//求最大值
hh=0,tt=-1;
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>=k-1) printf("%d ",a[q[hh]]);
}
cout<<endl;
return 0;
}