單調棧和單調隊列入門

單調棧是什麼?

分爲單調遞增和單調遞減棧。(棧內元素成遞增或者遞減性)c++

例如:
當前單調遞增棧內元素[1,2,4],以後入棧的元素爲(3),
爲了維護單調性,元素(4)出棧數組

\[ [1,2,4]-入棧(3) -出棧(4)- [1,2,3]-入棧(0)-出棧(1)(2)(3)-[0] \]編碼

單調遞增棧主要做用:

把序列中每一個元素放到單調棧中進行維護就能夠在\(O(n)\)時間複雜度內
求出區間每一個元素爲最大值/最小值時,影響的區間範圍爲[left,right]。spa

單調遞增↗棧求最小值影響範圍code

單調遞減↘棧求最大值影響範圍隊列

\(例如:序列{1,2,3,2,1}\)ci

1 2 3 2 1
        口
      口口口
    口口口口口
    0 1 2 3 4

用單調遞減棧便可求出get

最大值 區間[left,right]
1 [0,0]
2 [0,1]
3 [0,4]
2 [3,4]
1 [4,4]

維護單調棧:

這裏咱們以單調遞增棧爲例,求最小值影響範圍

咱們規定將下標(index)壓入棧中。爲了方便編碼,咱們在使用單調棧的數組的最後加入-INF(一個極小值),方便後續的出棧。it

序列變成 \({1,2,3,2,1,-INF}\)table

i 要入棧的height[i] 棧的變更 變更後的棧
0 1 push(0) [0]
1 2 push(1) [0,1]
2 3 push(2) [0,1,2]
3 2 pop(2),push(3) [0,1,3]
4 1 pop(3),pop(1),push(4) [0,4]
5 -INF pop(0),push(4) []

[left,right]中的right:

若元素height[i]從棧中pop出就說明這個元素爲最小值的右側影響範圍到此爲止。

[left,right]中的left:

由於棧內元素單調遞增,棧pop以後,棧頂的元素height[s.top()]不大於pop的元素。因此左側的影響範圍爲pop以後棧頂的元素的下標+1,這裏須要注意pop以後棧爲空的狀況,由於pop以後棧爲空,說明沒有元素是比pop出的這個元素小,那這個pop出的元素影響它左端的全部元素。

//單調遞增棧
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long int LL;
const int MAXN = 1e6 + 1;
LL height[MAXN];
int N;

void solve(){
    height[N] = -INF;
    stack<int> s;
    for(int i=0;i<=N;i++){
        while(!s.empty() && height[s.top()] > height[i]){
            int cur = s.top();
            s.pop();
            int _left = s.empty()?0:s.top()+1;
            int _right = i-1;
            cout << height[cur] << " " << _left << " " << _right << endl;
        }
        s.push(i);
    }
}

int main() {
    cin >> N;
    for(int i=0;i<N;i++) cin >> height[i];
    solve();
    return 0;
}
//單調遞減棧
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long int LL;
const int MAXN = 1e6 + 1;
LL height[MAXN];
int N;

void solve(){
    height[N] = INF;
    stack<int> s;
    for(int i=0;i<=N;i++){
        while(!s.empty() && height[s.top()] < height[i]){
            int cur = s.top();
            s.pop();
            int _left = s.empty()?0:s.top()+1;
            int _right = i-1;
            cout << height[cur] << " " << _left << " " << _right << endl;
        }
        s.push(i);
    }
}

int main() {
    cin >> N;
    for(int i=0;i<N;i++) cin >> height[i];
    solve();
    return 0;
}

單調棧模板

void solve(){
    //單調遞增棧 -INF,遞減 INF
    height[N] = -INF;
    stack<int> s;
    for(int i=0;i<=N;i++){
        //單調遞增棧 >,遞減 <,等號看題目
        while(!s.empty() && height[s.top()] > height[i]){
            int cur = s.top();
            s.pop();
            int _left = s.empty()?0:s.top()+1;
            int _right = i-1;
            cout << height[cur] << " " << _left << " " << _right << endl;
        }
        s.push(i);
    }
}

單調隊列

引入雙端隊列的概念。
元素能夠從隊列的頭部和尾部進行插入和刪除。

那麼單調隊列和單調棧的區別在於棧與雙端隊列的區別,在原有單調棧的基礎上,你能夠修改和獲取到棧底的元素,這就致使了你能夠對最值影響區間[Left,Right]中的Left進行控制,而且能夠直接得到這個區間最值是多少。(本來由於棧頂元素未知,因此沒法獲取),也就是說能夠 \(O(n)\)求整個序列中,區間長度爲k的區間最值

//輸出區間[left,right],長度爲m的最小值.
inline void solve(){
    deque<int> q;
    for(int i=0;i<n;i++) {
        //單調遞增棧 >,遞減 <
        while(!q.empty()&&height[q.back()]>height[i]) q.pop_back();
                //如下根據題意進行更改
        printf(i>0?"0\n":"%d\n",height[q.front()]);
        q.push_back(i);
        if(i-q.front()>=m) q.pop_front();//限制區間長度爲m
    }
    putchar('\n');
}

題目

P1886 滑動窗口

P1440 求m區間內最小值

相關文章
相關標籤/搜索