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)
单调栈标准写法:
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;
}
};
单调栈添加虚拟边界:
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;
}
};
一遍扫描法:
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;
}
};
数组模拟栈实现:
#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;
}
一遍扫描法:
#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;
}