單調佇列
我們從最簡單的問題開始:
eg. 現在有乙個數列,假設它有n項,問題是:求出每個區間長度為k的連續區間內數列的最大值。
數學表示式: f(i) = max,i = 0,1,…,n-1。
解法一:
很直觀的一種解法,那就是從數列的開頭,將窗放上去,然後找到這最開始的k個數的最大值,然後窗最後移乙個單元,繼續找到k個數中的最大值。
這種方法每求乙個f(i),都要進行k-1次的比較,複雜度為o(n*k)。
那麼有沒有更快一點的演算法呢?
解法二:
我們知道,上一種演算法有乙個地方是重複比較了,就是在找當前的f(i)的時候,i的前面k-1個數其它在算f(i-1)的時候我們就比較過了。那麼我們能不能儲存上一次的結果呢?當然主要是i的前k-1個數中的最大值了。答案是可以,這就要用到單調遞減佇列。
單調遞減佇列是這麼乙個佇列,它的頭元素一直是佇列當中的最大值,而且佇列中的值是按照遞減的順序排列的。我們可以從佇列的末尾插入乙個元素,可以從佇列的兩端刪除元素。同理單調遞增佇列。
1.首先看插入元素:為了保證佇列的遞減性,我們在插入元素v的時候,要將隊尾的元素和v比較,如果隊尾的元素不大於v,則刪除隊尾的元素,然後繼續將新的隊尾的元素與v比較,直到隊尾的元素大於v,這個時候我們才將v插入到隊尾。
2.隊尾的刪除剛剛已經說了,那麼隊首的元素什麼時候刪除呢?由於我們只需要儲存i的前k-1個元素中的最大值,所以當隊首的元素的索引或下標小於i-k+1的時候,就說明隊首的元素對於求f(i)已經沒有意義了,因為它已經不在窗裡面了。所以當index[隊首元素]為了讓讀者更明白一點,我舉個簡單的例子。
假設數列為:8,7,12,5,16,9,17,2,4,6.n=10,k=3.
那麼我們構造乙個長度為3的單調遞減佇列:
首先,那8和它的索引0放入佇列中,我們用(8,0)表示,每一步插入元素時佇列中的元素如下:
佇列的狀態
第1個數入隊:8
第2個數入隊:7
第3個數入隊: 7 12
第4個數入隊: 5
第5個數入隊:5 16
第6個數入隊:5 9
第7個數入隊:9 17
第8個數入隊:2
第9個數入隊:2 4
第10個數入隊:2 4 6
取隊首 877
5599
222區間為3,所以前兩個數產生的最小值捨棄,從第3個數開始,每入隊後取隊首正好是區間為3 的最小值,
現在佇列裡剛好沒有超過區間的,如果超過區間就的
把從隊首刪除。
**如下:
#include
#include
#include
#include
#include
using
namespace std;
const
int maxn=
100007
;int a[maxn]
,q[maxn]
,s[maxn]
;//s儲存每個區間的最小值,q是模擬佇列陣列
intmain()
int head=
1,tail=0;
q[0]=
0;for(
int i=
1;i<=n;i++
) q[
++tail]
=i;//i入隊
if(i-q[head]
>=m)
//當前數下標減去隊首儲存下標大於等於m,說明超出區間
//注意q儲存的是下標而不是數值
for(
int j=head;j<=tail;j++
)printf
("%d "
,a[q[j]])
;printf
("\n");
s[i]
=a[q[head]];
//拿隊頭出來,就是拿區間最小值,根據演算法需要把隊頭拿出來計算
}for
(int i=
3;i<=n;i++
)printf
("%d "
,s[i]);
return0;
}//單調佇列,一定要注意,不超區間,隊首就不刪
模板 單調佇列
單調佇列 詳細介紹新增鏈結描述 其實就是維護乙個嚴格單調的佇列 或棧 保證這個佇列是單調的且佇列中的元素的順序和和之前的前後順序保持一致 維護的時候 如果新的元素比隊尾的元素小 假如是單調增佇列 那麼一直pop,直到新的元素比隊尾元素大 直接加入隊尾 或者和隊尾元素相等 不用操作,因為該元素已經在佇...
單調佇列模板
單調佇列 適用於 需要快速求出區間最大 最小值的情況 常用於dp優化 相關演算法 線段樹 單調佇列 量短,速度快,視題意選擇 單調棧原理 思路 每次有元素進隊時,找到合適的隊尾接上,保證佇列單調性。刪除後面不必要的一段 同時,維護可用區間,及時去頭。includeusing namespace st...
模板 單調佇列
現在有一堆數字共n個數字 n 10 6 以及乙個大小為k的視窗。現在這個從左邊開始向右滑動,每次滑動乙個單位,求出每次滑動後視窗中的最大值和最小值。例如 the array is 1 3 1 3 5 3 6 7 and k 3.輸入格式 輸入一共有兩行,第一行為n,k。第二行為n個數 輸出格式 輸出...