單調隊列 —— 滑動窗口
dequeue雙向隊列
dequeue<int>que;//創建雙向隊列
que.push_front()//在隊列前面塞一個元素
que.push_back()//在隊列後面塞一個元素
que.pop_front()//刪除隊列第一個元素
que.pop_back()//刪除隊列的最後一個元素
que.clear()//清空隊列
que.empty()//判斷是否為空
que.size()//返回隊列元素個數
單調隊列
問題:
對每個長度為k的滑動窗體,求其最大值和最小值
思路1:
使出秘技dequeue (STL賽高!)
這裡根據雨巨生動形象的例子,我來簡單描述一下下:
題意:給出各屆acmer的實力,眾所周知大學基本上是四年,所以我們滑動窗口的大小就是4,問每個窗口中實力最強與最弱的是多少
舉個樣例:1 4 6 3 3 3 2
我們現在先求最大值,最小值在最大值的基礎上改一改就行。
首先我們看給1入隊列(是dequeue,後面不贅述),因為沒到達四個元素,所以不輸出,再到第二個元素4,大於1,說明在4的偉大作用下,1已經廢了,就直接扔出去,也就是「去尾」,得到隊列 4 。再到6,6 > 4,所以在6的存在下,4 廢了,扔出去,得到隊列 6。再到3,3 小於6,說明他有機會,因為可以等到6的退役了(也就是6滑出窗口了),他就可能是最大的,把 3塞進去,得到6,3的隊列,現在達到窗口的大小,所以輸出隊頭6,再看3,等於隊列尾的元素,就把他也塞進去,再輸出一個6,下一個3塞進來以後,我們判斷發現隊列頭還沒有出窗口,就再輸出6,到了2,塞進去,發現6已經出了窗口,就得「去頭」,所以輸出3
總結一下,實現單調隊列,主要分四個部分:
- 去尾:隊尾元素出隊列。當有新的acmer等待入隊時,需要從隊尾開始判斷會不會破壞隊的單調性,會的話就去尾。
- 入隊:將新隊員從隊尾塞進來
- 去頭:隊頭元素出隊列。當acmer滿四年後就得退役,也就是要將其扔出隊列
- 取解:取隊頭元素
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <stdlib.h>
#include <sstream>
#include <map>
#include <set>
using namespace std;
#define inf 0x3f3f3f3f
#define MAX 1000000 + 5
typedef long long ll ;
inline int IntRead(){//int的快讀
char ch = getchar();
int s = 0, w = 1;
while(ch < '0' || ch > '9'){
if(ch == '-') w = -1;
ch = getchar();}
while(ch >= '0' && ch <= '9'){
s = s * 10 + ch - '0';
ch = getchar();}
return s * w;
}
int tr[MAX];
int main()
{
int n, k;
deque<int>q;//雙向隊列
cin>>n>>k;
for(int i = 1; i <= n; i++)
tr[i] = IntRead();
for(int i = 1; i <= n; ++i){
while (!q.empty() && tr[i] < tr[q.back()]) {
q.pop_back();//去尾
}
q.push_back(i);//入隊
if(i >= k){//從第k個開始,後面都得輸出
while (!q.empty() && i - q.front() + 1 > k) {
q.pop_front();//去頭
}
if(i == k) cout<<tr[q.front()];//控制空格的輸出
else cout<<' '<<tr[q.front()];
}
}
cout<<endl;
q.clear();//清空隊列
for(int i = 1; i <= n; i++){
while (!q.empty() && tr[q.back()] < tr[i]) {
q.pop_back();
}
q.push_back(i);
if(i >= k){
while (!q.empty() && i - q.front() + 1 > k) {
q.pop_front();
}
if(i == k)
cout<<tr[q.front()];
else
cout<<' '<<tr[q.front()];
}
}
cout<<endl;
}
思路2:
手寫隊列
head代表隊列頭,tail代表隊列尾
結構體的q數組即為「雙向隊列」,其id用來記錄下標,val來記錄值
還是那四步,去尾入隊去頭取解。
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <stdlib.h>
#include <sstream>
#include <map>
#include <set>
using namespace std;
#define inf 0x3f3f3f3f
#define MAX 1000000 + 5
typedef long long ll ;
inline int IntRead(){//int的快讀
char ch = getchar();
int s = 0, w = 1;
while(ch < '0' || ch > '9'){
if(ch == '-') w = -1;
ch = getchar();}
while(ch >= '0' && ch <= '9'){
s = s * 10 + ch - '0';
ch = getchar();}
return s * w;
}
int tr[MAX], n, k;
struct ran{
int id, val;
}q[MAX];
void getmin(){
int head = 1, tail = 0;//初始化
for(int i = 1; i <= n; ++i){
while (head <= tail && tr[i] < q[tail].val) tail--;//去尾
q[++tail].id = i;//入隊的時候記得要記錄兩部分
q[tail].val = tr[i];
while (i - q[head].id + 1 > k) head++;//去頭
if(i >= k){
if(i == k)cout<<q[head].val;//控制空格,防止PE
else cout<<' '<<q[head].val;
}
}
cout<<endl;
}
void getmax(){
int head = 1, tail = 0;
for(int i = 1; i <= n; ++i){
while (head <= tail && q[tail].val < tr[i]) tail--;
q[++tail].id = i;
q[tail].val = tr[i];
while (i - q[head].id + 1 > k) head++;
if(i >= k){
if(i == k)cout<<q[head].val;
else cout<<' '<<q[head].val;
}
}
cout<<'\n';
}
int main()
{
cin>>n>>k;
for(int i = 1; i <= n; ++i) tr[i] = IntRead();
getmin();
getmax();
return 0;
}
我看了一下這兩個的時間差的不多,2ms,我感覺還是dequeue好寫一些,嘎嘎,感覺寫成函數更美觀一點哎=(.)=