滑動視窗(單調佇列)
題目描述
給定一個大小為n≤106的陣列。
有一個大小為k的滑動視窗,它從陣列的最左邊移動到最右邊。
您只能在視窗中看到k個數字。
每次滑動視窗向右移動一個位置。
以下是一個例子:
該陣列為[1 3 -1 -3 5 3 6 7],k為3。
您的任務是確定滑動視窗位於每個位置時,視窗中的最大值和最小值。
輸入格式
輸入包含兩行。
第一行包含兩個整數n和k,分別代表陣列長度和滑動視窗的長度。
第二行有n個整數,代表陣列的具體數值。
同行資料之間用空格隔開。
輸出格式
輸出包含兩個。
第一行輸出,從左至右,每個位置滑動視窗中的最小值。
第二行輸出,從左至右,每個位置滑動視窗中的最大值。
輸入樣例:
8 3
1 3 -1 -3 5 3 6 7
輸出樣例:
-1 -3 -3 -3 3 3
3 3 5 5 6 7
演算法 單調佇列 O(n)
單調佇列的性質
1.佇列中的元素其對應在原來的列表中的順序必須是單調遞增的。
2.佇列中元素的大小必須是單調遞*(增/減/甚至是自定義也可以)
3.單調佇列與普通佇列不一樣的地方就在於單調佇列既可以從隊首出隊,也可以從隊尾出隊
因此不能用stl中的queue,我們可以用陣列模擬或者deque
模擬過程
下文中我們用q來表示單調佇列,p來表示其所對應的在原列表裡的序號。
由於此時隊中沒有一個元素,我們直接令1進隊。此時,q={1},p={1}。
現在3面臨著抉擇。下面基於這樣一個思想:假如把3放進去,如果後面2個數都比它大,那麼3在其有生之年就有可能成為最小的。此時,q={1,3},p={1,2}
下面出現了-1。隊尾元素3比-1大,那麼意味著只要-1進隊,那麼3在其有生之年必定成為不了最小值,原因很明顯:因為當下面3被框起來,那麼-1也一定被框起來,所以3永遠不能當最小值。所以,3從隊尾出隊。同理,1從隊尾出隊。最後-1進隊,此時q={-1},p={3}
出現-3,同上面分析,-1>-3,-1從隊尾出隊,-3從隊尾進隊。q={-3},p={4}。
出現5,因為5>-3,同第二條分析,5在有生之年還是有希望的,所以5進隊。此時,q={-3,5},p={4,5}
出現3。3先與隊尾的5比較,3<5,按照第3條的分析,5從隊尾出隊。3再與-3比較,同第二條分析,3進隊。此時,q={-3,3},p={4,6}
出現6。6與3比較,因為3<6,所以3不必出隊。由於3以前元素都<3,所以不必再比較,6進隊。因為-3此時已經在滑動視窗之外,所以-3從隊首出隊。此時,q={3,6},p={6,7}
出現7。隊尾元素6小於7,7進隊。此時,q={3,6,7},p={6,7,8}。
那麼,我們對單調佇列的基本操作已經分析完畢。因為單調佇列中元素大小單調遞*(增/減/自定義比較),因此,隊首元素必定是最值。按題意輸出即可。
參考 https://www.luogu.com.cn/blog/hankeke/solution-p1886
c++程式碼
#include<bits/stdc++.h>
using namespace std;
const int N = 1000010;
int a[N], q[N];//q是單調佇列,存的是a陣列元素對應的下標,而不是元素
int n, k;
int main()
{
cin >> n >> k;
for(int i = 0; i < n; i ++) cin >> a[i];
int hh = 0, tt = -1;
for(int i = 0; i < n; i ++)
{
while(hh <= tt && a[i] <= a[q[tt]]) tt --;//如果隊尾元素比當前要進隊的元素值大的話,那麼隊尾元素在其有生之年都不可能成為最小值,就應該出隊
q[++ tt] = i;
if(hh <= tt && i - k + 1 > q[hh]) hh ++;//判斷隊首元素是否滑出了當前的視窗,一次最多從隊首滑出一個元素,因此用if判斷
if(i >= k - 1) cout << a[q[hh]] << ' ';
}
cout << endl;
hh = 0, tt = -1;
for(int i = 0; i < n; i ++)
{
while(hh <= tt && a[i] >= a[q[tt]]) tt --;
q[++ tt] = i;
if(hh <= tt && i - k + 1 > q[hh]) hh ++;
if(i >= k - 1) cout << a[q[hh]] << ' ';
}
}