1.单调队列简介
单调队列是一个数据结构,并不是STL
里面的内容。
单调队列为何说单调,因为是队列中的元素始终保持着单增或者单减的特性。
单调队列的核心功能:
在每次加入或者删除元素时都保持序列里的元素有序,即队首元素始终是最小值或者最大值
举例,以下面数组为例,数组中的元素插入单调队列中
a[] = {3,1,15,10,7}
假设队列是单调递减的,那么队首元素始终是最大的,五次操作后的队列元素排列情况如下
第 i 次操作插入的元素 | 结果 | 详细过程 |
3 | [3] | 队列里没有元素,3插入到队列中 |
1 | [3,1] | 1小于队头元素,加入到队列中 |
15 | [15] | 15大于当前元素,且大于队头元素 1,3出队列,15入队列 |
10 | [15,10] | 10小于队头元素,可以加入队列 |
7 | [15,10,7] | 7小于当前元素,可以入队列 |
数组中五个元素入队列后,排列结果结果为[15,10,7],队头元素为最大值,队列单调递减
2.单调队列的实现及模版
用一道模板题实现以下单调队列
思路:
1.创建一个双端队列,找最小值为例
2.创建队列后,队列中没有值,1直接插入
(注意:插入的是元素的下标,为了判断判断存的元素是否在窗口内)
3.现在3面临抉择,插入3后,如果后面两个数比3大,那么3有可能会是最小值 ,3进队
4.下面出现了-1,-1比队尾元素小,意味着只要-1进队,3永远不可能成为最小,那么就让3出队,同理,1也应该出队
5.按照这样的规律,我们可以总结出:
当队尾元素大于等于当前元素时 即 a[q.back()] >=a[i]
就让队尾出队 q.pop_back() 再存入当前元素的下标,放到队尾
当队尾元素小于当前元素时 即 a[q.back()] < a[i]
存入当前元素的下标,放到队尾
还要检查队头元素的位置,判断队头是否在窗口内,q.back() - q.front()+1 要小于k
如果q.back() - q.front()+1 > k 队头元素要出队列 q.pop_front()
找最大值同理,仅需改变符号。
当队尾元素小于等于当前元素时 即 a[q.back()] <=a[i]
就让队尾出队 q.pop_back() 再存入当前元素的下标,放到队尾
模版:
#include<iostream>
#include<deque>
using namespace std;
const int N = 1e6+10;
typedef long long LL;
LL n,k;
LL a[N];
int main()
{
cin>>n>>k;
for(int i = 1;i <= n;i++) cin>>a[i];
deque<LL> q;
for(int i = 1;i <= n;i++)
{
while(q.size() && a[q.back()] >= a[i]) q.pop_back();
q.push_back(i);
if(q.back() - q.front() + 1 > k) q.pop_front();
if(i>=k) cout<<a[q.front()]<<" ";
}
cout<<endl;
q.clear();
for(int i = 1;i <= n;i++)
{
while(q.size() && a[q.back()] <= a[i]) q.pop_back();
q.push_back(i);
if(q.back() - q.front() + 1 > k) q.pop_front();
if(i>=k) cout<<a[q.front()]<<" ";
}
cout<<endl;
return 0;
}
3.经典例题
#include<iostream>
#include<deque>
using namespace std;
const int N = 1e6+10;
typedef long long LL;
LL q[N],A[N];
LL n,m;
int main()
{
cin>>n>>m;
for(int i = 1;i <= n;i++) cin>>A[i];
deque<LL> q;
for(int i = 1;i <= n;i++)
{
while(q.size() && A[q.back()] >= A[i]) q.pop_back();
q.push_back(i);
if(q.back()-q.front()+1 > m) q.pop_front();
if(i>=m) cout<<A[q.front()]<<endl;
}
return 0;
}