【数据结构】单调栈专题

模板题:830. 单调栈

关键要理解的地方是什么时候就弹出栈顶。本题找的是距离x最近的比x小的数,所以栈里的数如果大于等于x那么就一定不会被用到(因为x比栈顶元素更优),所以可以全部删掉(弹出),最后剩下的栈顶元素就是答案。

STL版本

#include <iostream>
#include <stack>

using namespace std;

stack<int> stk;

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )
    {
        int x;
        scanf("%d", &x);
        while (stk.size() && stk.top() >= x) stk.pop();
        if (stk.size()) printf("%d ", stk.top());
        else printf("-1 ");
        stk.push(x);
    }
    return 0;
}

作者:NFYD
链接:https://www.acwing.com/activity/content/code/content/1161663/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

数组模拟栈

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1e5 + 10;

int stk[N], tt;

int main()
{
    int n;
    scanf("%d", &n);
    while(n -- )
    {
        int x;
        scanf("%d", &x);
        while(tt && stk[tt] >= x) tt -- ;
        if(tt) printf("%d ", stk[tt]);
        else printf("-1 ");
        stk[++ tt] = x;
    }
    return 0;
}

131. 直方图中最大的矩形

本题要利用单调栈来优化时间复杂度。
要找到左边(右边同理)第一个比当前矩形矮的矩形,所以当栈顶元素的高度大于等于当前高度h[i],那么栈顶元素就一定不会被用到(因为当前高度更优),可以删掉(弹出),重复此操作直到栈顶元素的高度小于当前高度或栈空(无解)为止

小细节:让h[1]h[n + 1] = 1,确保所有矩形两边都有比其矮的矩形,可以方便处理边界问题。

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long LL;

const int N = 1e5 + 10;

int n;
int h[N], l[N], r[N], q[N];

int main()
{
    while(scanf("%d", &n), n)
    {
        for(int i = 1; i <= n; i ++ ) scanf("%d", &h[i]);
        h[0] = h[n + 1] = -1;
        int tt = 0;
        q[0] = 0;
        for(int i = 1; i <= n; i ++ )
        {
            while(h[i] <= h[q[tt]]) tt -- ;
            l[i] = q[tt];
            q[++ tt] = i;
        }

        tt = 0;
        q[0] = n + 1;
        for(int i = n; i; i -- )
        {
            while(h[i] <= h[q[tt]]) tt -- ;
            r[i] = q[tt];
            q[++ tt] = i;
        }

        LL res = 0;
        for(int i = 1; i <= n; i ++ )
            res = max(res, (LL)h[i] * (r[i] - l[i] - 1));
        printf("%lld\n", res);
    }
    return 0;
}

作者:Once.
链接:https://www.acwing.com/activity/content/code/content/3210396/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

152. 城市游戏

和直方图中的最大矩形基本一样,读入的时候需要稍微处理一下.
s[i][j]表示坐标为(i, j)的格子上方有多少个连续的F(相当于构造直方图)
递推关系:一个格子上方的连续F数=其上一个格子s[i - 1][j]的连续F数+1
image

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1010;

int n, m;
int s[N][N], l[N], r[N], q[N];

int work(int h[])
{
    int tt = 0;
    h[0] = h[m + 1] = -1;
    q[0] = 0;
    for(int i = 1; i <= m; i ++ )
    {
        while(h[q[tt]] >= h[i]) tt -- ;
        l[i] = q[tt];
        q[++ tt] = i;
    }

    tt = 0;
    q[0] = m + 1;
    for(int i = m; i; i -- )
    {
        while(h[q[tt]] >= h[i]) tt -- ;
        r[i] = q[tt];
        q[++ tt] = i;
    }

    int res = 0;
    for(int i = 1; i <= m; i ++ ) 
        res = max(res, h[i] * (r[i] - l[i] - 1));

    return res;
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i ++ )
        for(int j = 1; j <= m; j ++ )
        {
            char c;
            cin >> c;
            if(c == 'F') s[i][j] = s[i - 1][j] + 1;
        }

    int res = 0;
    for(int i = 1; i <= n; i ++ ) res = max(res, work(s[i]));
    printf("%d\n", res * 3);
    return 0;
}

作者:Once.
链接:https://www.acwing.com/activity/content/code/content/3216854/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1574. 接雨水

本题单调栈找的是一个矩形两边第一个比其高的矩形,当栈顶元素小于等于当前元素,说明当前元素是更优解,即栈顶元素永远不会作为答案,所以可以删掉(出栈),然后将当前元素入栈,然后在维护单调栈的过程中顺便把雨水面积求出。
雨水面积是一块一块地算,当当前元素高度大于等于栈顶,先把栈顶元素与当前元素之间的雨水算一下,然后栈顶元素出栈。
image

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1e5 + 10;

int n;
int h[N], q[N];

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++ ) scanf("%d", &h[i]);

    int tt = -1;
    int res = 0;
    for(int i = 1; i <= n; i ++ )
    {
        int last = 0;
        while(tt >= 0 && h[q[tt]] <= h[i]) 
        {
            res += (h[q[tt]] - last) * (i - q[tt] - 1);
            last = h[q[tt]];
            tt -- ;
        }
        if(tt >= 0) res += (h[i] - last) * (i - q[tt] - 1);
        q[++ tt] = i;
    }
    printf("%d\n", res);
    return 0;
}

作者:Once.
链接:https://www.acwing.com/activity/content/code/content/3218137/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

600. 仰视奶牛

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1e5 + 10;

int n;
int h[N];
int stk[N], tt;
int s[N];

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++ ) scanf("%d", &h[i]);
    
    for(int i = 1; i <= n; i ++ )
    {
        while(tt && h[stk[tt]] < h[i])
        {
            s[stk[tt]] = i;
            tt -- ;
        }
        stk[++ tt] = i;
    }
    
    for(int i = 1; i <= n; i ++ ) printf("%d\n", s[i]);
    return 0;
}

1413. 矩形牛棚

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 3010;

int n, m, p;
int g[N][N], h[N][N], stk[N];
int l[N], r[N];

int work(int h[])
{
    int tt = 0;
    for(int i = 1; i <= m; i ++ )
    {
        while(tt && h[stk[tt]] >= h[i]) tt -- ;
        if(!tt) l[i] = 0;
        else l[i] = stk[tt];
        stk[++ tt] = i;
    }

    tt = 0;
    for(int i = m; i; i -- )
    {
        while(tt && h[stk[tt]] >= h[i]) tt -- ;
        if(!tt) r[i] = m + 1;
        else r[i] = stk[tt];
        stk[++ tt] = i;
    }

    int res = 0;
    for(int i = 1; i <= m; i ++ )
        res = max(res, h[i] * (r[i] - l[i] - 1));
    return res;
}

int main()
{
    scanf("%d%d%d", &n, &m, &p);
    while(p -- )
    {
        int x, y;
        scanf("%d%d", &x, &y);
        g[x][y] = 1;
    }

    for(int i = 1; i <= n; i ++ )
        for(int j = 1; j <= m; j ++ )
            if(!g[i][j])
                h[i][j] = h[i - 1][j] + 1;

    int res = 0;    
    for(int i = 1; i <= n; i ++ ) res = max(res, work(h[i]));
    printf("%d\n", res);
    return 0;
}

作者:Once.
链接:https://www.acwing.com/activity/content/code/content/3276445/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

41. 包含min函数的栈

开一个辅助栈stk_min,用于处理get_min()函数
每次加入新元素时,原栈stk正常加入,辅助栈加入栈顶元素与新元素中较小的那个元素。
这样辅助栈存的就是前i个加入的数中的最小值,每次询问时只需输出栈顶即可。
每次弹栈时,辅助栈和原栈操作相同。

class MinStack {
public:
    /** initialize your data structure here. */
    
    stack<int> stk, stk_min;
    
    MinStack() {
        
    }
    
    void push(int x) {
        stk.push(x);
        if(stk_min.size()) x = min(x, stk_min.top());
        stk_min.push(x);
    }
    
    void pop() {
        stk.pop();
        stk_min.pop();
    }
    
    int top() {
        return stk.top();
    }
    
    int getMin() {
        return stk_min.top();
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

写法二:维护一个单调递减的栈

class MinStack {
public:
    /** initialize your data structure here. */
    
    stack<int> stk, stk1;
    MinStack() {
        
    }
    
    void push(int x) {
        stk.push(x);
        if(stk1.empty() || stk1.top() >= x) stk1.push(x);
    }
    
    void pop() {
        if(stk1.top() == stk.top()) stk1.pop();
        stk.pop();
    }
    
    int top() {
        return stk.top();
    }
    
    int getMin() {
        return stk1.top();
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

LeetCode 456. 132模式

下图来自leetcode评论区“宫水三叶
image

自己的话:
k代表的是第二大的元素,用出栈的元素更新k(有元素出栈就代表有更大的元素要入栈,所以k是第二大的元素),栈里的始终是最大的元素。所以当有nums[i] < k就认为找到了一组解。

class Solution {
public:
    bool find132pattern(vector<int>& nums) {
        int k = INT_MIN;
        stack<int> stk;
        for(int i = nums.size() - 1; i >= 0; i -- )
        {
            if(nums[i] < k) return true;
            while(stk.size() && stk.top() < nums[i])
            {
                k = max(k, stk.top());
                stk.pop();
            }
            stk.push(nums[i]);
        }
        return false;
    }
};

1575. 盛水最多的容器

本题的传统方法是单调栈,但是本题有一个巧解是双指针。


i,j两个指针,先计算面积
然后每次比较i,j,小的那个向中间移动一位(若相等则谁移动都可以),然后计算面积,在所有面积中取最大值即可

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1e5 + 10;

int n;
int h[N];

int main()
{
    scanf("%d", &n);
    for(int i = 0; i < n; i ++ ) scanf("%d", &h[i]);

    int res = 0;
    for(int i = 0, j = n - 1; i < j;)
    {
        res = max(res, min(h[i], h[j]) * (j - i));
        if(h[i] < h[j]) i ++ ;
        else j -- ;
    }
    printf("%d\n", res);
    return 0;
}

作者:Once.
链接:https://www.acwing.com/activity/content/code/content/3214975/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
posted @ 2022-04-12 18:42  Tshaxz  阅读(32)  评论(0编辑  收藏  举报
Language: HTML