什麼是單調隊列,有什麼用?
不妨用一個問題來說明單調隊列的作用和操作:
不斷地向緩存數組裏讀入元素,也不時地去掉最老的元素,不定期的詢問當前緩存數組裏的最小的元素。
最直接的方法:普通隊列實現緩存數組。
進隊出隊都是O(1),一次查詢需要遍歷當前隊列的所有元素,故O(n)。
單調隊列的運用
假設數列:7 8 1 1 4 3 2
現在我們要求出以3個元素爲區間大小的條件下,每個區間的最小值。
初步判斷:
第一個區間:7 8 1最小值爲 1
第二個區間:8 1 1最小值爲 1
第三個區間:1 1 4最小值爲 1
第四個區間:1 4 3最小值爲 1
第五個區間:4 3 2最小值爲 2
單調隊列如何實現?
以上面假設爲例,我們要維護一個單調遞增的隊列。
從單調隊列的定義我們知道,數列中的每個元素都要進隊列,那麼隊列有進自然就有出,那出隊又是怎麼實現的呢?也許不應該稱爲出隊,稱爲覆蓋會更爲恰當。
我們需要不斷更新以下幾點(變量定義):
①、已入隊元素數量 i
②、隊頭元素在數列中的序號 start
③、隊尾元素在數列中的序號 end
④、區間大小m
輸出隊頭元素要滿足三個條件:
①、該隊列符合遞增。
②、i - start < m。
③、i >= m 。
隊列實現步驟
爲了更清晰展現實現過程,下文將用變量來代替部分文字。
若對變量定義不理解的朋友,請看上方 變量定義 。
①、首元素入隊:7
變量: i = 1, start = 1, m = 3
分析:
1、該隊列符合遞增,符合。
2、i - start < m,符合。
3、i < m ,不符合。
結果:不輸出隊頭元素 7 。
②、入隊:8
變量: i = 2, start = 1, m = 3
分析:
1、該隊列符合遞增,符合。
2、i - start < m,符合。
3、i < m ,不符合。
結果:不輸出隊頭元素 7 。
③、入隊:1
變量: i = 3, start = 1, m = 3
分析:
1、該隊列不符合遞增。
操作:捨棄隊列中比第三個元素1大的元素7 8。
變量: i = 3, start = 3, m = 3
分析:
1、該隊列符合遞增,符合。
2、i - start < m,符合。
3、i >= m ,符合。
結果:輸出隊頭元素 1 。
④、入隊:1
變量: i = 4, start = 3, m = 3
分析:
1、該隊列符合遞增,符合。
2、i - start < m,符合。
3、i >= m ,符合。
結果:輸出隊頭元素 1 。
⑤、入隊:4
變量: i = 5, start = 3, m = 3
分析:
1、該隊列符合遞增,符合。
2、i - start < m,符合。
3、i >= m ,符合。
結果:輸出隊頭元素 1 。
⑥、入隊:3
變量: i = 6, start = 3, m = 3
分析:
1、該隊列不符合遞增。
操作:捨棄隊列中比第六個元素3大的元素 4 。
變量: i = 6, start = 3, m = 3
分析:
1、該隊列符合遞增,符合。
2、i - start == m ,不符合。
變量: i = 6, start = 4, m = 3
分析:
1、該隊列符合遞增,符合。
2、i - start < m,符合。
3、i >= m ,符合。
結果:輸出隊頭元素 1 。
⑦、入隊:2
變量: i = 7, start = 4, m = 3
分析:
1、該隊列不符合遞增。
操作:捨棄隊列中比第七個元素2大的元素 3 。
變量: i = 7, start = 4, m = 3
分析:
1、該隊列符合遞增,符合。
2、i - start < m,不符合。
變量: i = 7, start = 5, m = 3
分析:
1、該隊列符合遞增,符合。
2、i - start < m,符合。
3、i >= m ,符合。
結果:輸出隊頭元素 2 。
最終輸出:1 1 1 1 2
與初步判斷輸出相同!
完成!
習題
題目描述
一個含有n項的數列(n<=2000000),求出每一項前的m個數到它這個區間內的最小值。若前面的數不足m項則從第1個數開始,若前面沒有數則輸出0。
輸入格式
第一行兩個數n,m。
第二行,n個正整數,爲所給定的數列。
輸出格式
n行,第i行的一個數ai,爲所求序列中第i個數前m個數的最小值。
輸入輸出樣例
輸入
6 2
7 8 1 4 3 2
輸出
0
7
7
1
1
3
說明/提示
【數據規模】
m ≤ n ≤ 2000000
ai ≤ 3×107
洛谷測試平臺
運用單調遞增隊列的思想來完成本題。
①、創建結構數組,保存每項數列的數據及排列順序。
//維護一個單調遞增的動態隊列:start~end
struct node{
int x; //數據大小
int y; //排列順序
}q[2000010];
②、不斷維護一個單調遞增的動態隊列。
注意:隊列長度不定,有效的部分q[start] ~ q[end]。
for(int i=1; i<n; i++){
//start 元素不符合條件,該點已經不在查找的區間內
//end 始終不會大於 start+m
if(i-q[start].y>m){
start++;
}
printf("%d\n", q[start].x);
int tmp;
scanf("%d", &tmp);
//將 tmp 插入隊列 start~end 中
while(end>start && q[end-1].x>tmp){
end--;
}
q[end].x=tmp;//覆蓋原本q[end]
q[end].y=i;
end++;
}
整合代碼
#include<stdio.h>
struct node{
int x;
int y;
}q[2000010];
int main(){
int n, m, start=0, end=1;
scanf("%d%d", &n, &m);
scanf("%d", &q[0].x); //先插入隊列一個元素
//防止隊列爲空
q[0].y=0;
printf("0\n");
for(int i=1; i<n; i++){
if(i-q[start].y>m){
start++;
}
printf("%d\n", q[start].x);
int tmp;
scanf("%d", &tmp);
while(end>start && q[end-1].x>tmp){
end--;
}
q[end].x=tmp;
q[end].y=i;
end++;
}
return 0;
}
希望本文能讓你有所收穫……