[单调栈] lc84. 柱状图中最大的矩形、aw131. 直方图中最大的矩形(单调栈+算法对比+模板题)

1. 题目来源

链接:84. 柱状图中最大的矩形
链接:131. 直方图中最大的矩形

lc 官方题解一遍扫描法挺不错的,也给出了常见的单调栈几种写法。

2. 题目解析

单调栈裸题,官方题解给出的一遍扫描方法很不错,能拓展思路。

单调栈用来解决求左边第一个比当前数大的、小的数。同理也可以求右边。

顺序枚举每个位置当前高度,当前柱形能延展的矩形的左边界就是左边第一个比它小的数的位置,能延展的矩形的右边界就是右边第一个比它小的数的位置。

故矩形的高为当前高度,宽为 r-l-1,因为两边是取不到的。

注意边界问题,如果左边没有比它低的,那么它的左边界一直可以延伸到最左边,同理右边界也是如此。我们可以人为的将最左边、最右边放置一个 -1 高度的柱子,即一定有左右边界可以取到。也可以当栈为空的时候,就说明左边没有比它小的数了,那么将下标就置为 -1,代表 0 的左边 -1 位置是比当前数要小的。同理,当栈为空的时候,将右边的下标置为 n,代表 n-1 的右边是比当前数要小的。

添加了虚拟边界,并给栈中初始添加一个边界值,就不需要判断栈为空了,栈就不可能为空。

在此单调栈维护的是下标,因为需要计算宽度。


时间复杂度 O ( n ) O(n) O(n)
空间复杂度 O ( n ) O(n) O(n)


链接:84. 柱状图中最大的矩形

单调栈标准写法:

class Solution {
public:
    int largestRectangleArea(vector<int>& h) {
        int n = h.size();
        vector<int> l(n), r(n);
        stack<int> stk;

        for (int i = 0; i < n; i ++ ) {
            while (stk.size() && h[stk.top()] >= h[i]) stk.pop();
            if (stk.size()) l[i] = stk.top();
            else l[i] = -1;
            stk.push(i);
        }
        while (stk.size()) stk.pop();

        for (int i = n - 1; ~i; i -- ) {
            while (stk.size() && h[stk.top()] >= h[i]) stk.pop();
            if (stk.size()) r[i] = stk.top();
            else r[i] = n;
            stk.push(i);
        }

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

链接:84. 柱状图中最大的矩形

单调栈添加虚拟边界:

class Solution {
public:
    int largestRectangleArea(vector<int>& h) {
        int n = h.size();

        h.insert(h.begin(), -1);               // 加入虚拟边界,h[0] = -1, h[n + 1]= -1;
        h.push_back(-1);

        vector<int> l(n + 2), r(n + 2);		   // 注意初始化大小
        stack<int> stk;
        
        stk.push(0);                           // 加入虚拟下标,栈一定非空,h[0] = -1;
        for (int i = 1; i <= n; i ++ ) {
            while (h[stk.top()] >= h[i]) stk.pop();
            l[i] = stk.top();
            stk.push(i);
        }

        while (stk.size()) stk.pop();

        stk.push(n + 1);
        for (int i = n; i; i -- ) {
            while (h[stk.top()] >= h[i]) stk.pop();
            r[i] = stk.top();
            stk.push(i);
        }

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

链接:84. 柱状图中最大的矩形

一遍扫描法:

class Solution {
public:
    int largestRectangleArea(vector<int>& h) {
        int n = h.size();
        stack<int> s;
        vector<int> l(n), r(n, n);

        for (int i = 0; i < n; ++i) {
            while (s.size() && h[s.top()] >= h[i]) r[s.top()] = i, s.pop();
            if (s.empty()) l[i] = -1;
            else l[i] = s.top();
            s.push(i);
        }
        
        int res = 0;
        for (int i = 0; i < n; ++i) res = max(res, h[i] * (r[i] - l[i] - 1));
        
        return res;
    }
};

链接:131. 直方图中最大的矩形

数组模拟栈实现:

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

using namespace std;

typedef long long LL;

const int N = 1e5+5;

int n;
int h[N], l[N], r[N];
int stk[N], tt;

int main() {
    while (scanf("%d", &n), n) {
        for (int i = 1; i <= n; i ++ ) scanf("%d", &h[i]);
        h[0] = h[n + 1] = -1;
        
        tt = 0;
        stk[0] = 0;
        for (int i = 1; i <= n; i ++ ) {
            while (h[stk[tt]] >= h[i]) tt -- ;              // 栈内存下标,栈一定是不空的,因为一开始的栈底就是左边界的下标值
            l[i] = stk[tt];
            stk[ ++ tt ] = i;
        }
        
        tt = 0;
        stk[0] = n + 1;             // 栈底为右边界下标,n+1 位置,栈一定非空,h[n+1]=-1,一定不会出栈
        for (int i = n; i; i -- ) {
            while (h[stk[tt]] >= h[i]) tt -- ;              // 同理,栈内存下标,栈一定是不空的,因为一开始的栈底就是左边界的下标值
            r[i] = stk[tt];
            stk[ ++ 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;
}

链接:131. 直方图中最大的矩形

一遍扫描法:

#include <iostream>
#include <cstring>

using namespace std;

typedef long long LL;

const int N = 100005;

int n;
int a[N], l[N], r[N];
int stk[N];

int main() {
    while (cin >> n, n) {
        for (int i = 0; i < n; i ++ )  cin >> a[i];
        
        for (int i = 0; i < n; i ++ ) r[i] = n;
        
        int tt = -1;
        for (int i = 0; i < n; i ++ ) {
            while (tt >= 0 && a[stk[tt]] >= a[i]) r[stk[tt -- ]] = i;
            if (tt < 0) l[i] = -1;
            else l[i] = stk[tt];
            stk[++ tt] = i;
        }
        
        LL res = 0;
        for (int i = 0; i < n; i ++ ) res = max(res, (LL)a[i] * (r[i] - l[i] - 1));
        cout << res << endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值