84 柱状图中最大的矩形

题目链接:https://leetcode-cn.com/problems/largest-rectangle-in-histogram/

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

思考

我们可以反过来考虑针对于每一个元素,都可以求出以它为高的最大矩形面积

那么这样一个矩形,它的底应该是多少呢?

自然而然,我们可以发现:这一个矩形的左侧就是左边比它小的元素的位置,右侧自然也是比它小的元素的位置。

以下图的元素5为例,它的最左侧就应该是比它小的元素1的右边,它的最右侧就应该是比它小的元素2的左边。

image-20220331205119619

因此,问题就转化成了:针对于每一个元素,我们都需要求出左边比它小的元素的位置以及右边比它小的元素的位置。

单调栈

那么在从左往右遍历一遍的过程中,如何找出比它小的元素呢?

我们自然而然的想到了单调递增栈(严格单调递增):每当一个元素到达时候,不断出栈直到栈为空或者栈顶元素小于该元素,那么这个元素就一定是比它小的最近的元素。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function largestRectangleArea(heights: number[]): number {
// 元素个数
let n = heights.length;

let left = new Array(n).fill(-1);
let right = new Array(n).fill(n);

// 单调递增栈:严格单调递增,存储的是元素下标
let s = new Array();

// 从左往右遍历,计算left数组
for (let i = 0; i < n; ++i) {
// 出栈,直到栈空或者满足栈顶元素 < 当前元素
while (s.length != 0 && heights[s[s.length - 1]] >= heights[i]) {
s.pop();
}
if (s.length != 0) {
left[i] = s[s.length - 1];
}
// 入栈
s.push(i);
}

s.splice(0, s.length);
// 从右往左遍历,计算right数组
for (let i = n - 1; i >= 0; --i) {
// 出栈,直到栈空或者满足栈顶元素 < 当前元素
while (s.length != 0 && heights[s[s.length - 1]] >= heights[i]) {
s.pop();
}
if (s.length != 0) {
right[i] = s[s.length - 1];
}
// 入栈
s.push(i);
}

let maxRes = 0;
for (let i = 0; i < n; ++i) {
maxRes = Math.max(maxRes, (right[i] - left[i] - 1) * heights[i]);
}

return maxRes;
};

42 接雨水

题目链接:https://leetcode-cn.com/problems/trapping-rain-water/

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

img

思考

我们可以考虑计算每一根柱子所能接的雨水的高度。

这个高度应该是:左右两侧柱子的高度。其中这根柱子满足,它是这根柱子左边(或右边)比它高的那一根柱子所能接到雨水的高度。

注意:不是左边(或)右边比它高的柱子的高度,因为可能还有更高的柱子。实际上应该是接到雨水的高度,因为比它高的雨水能借到的柱子,它自己也一定能接到。

这样问题实际上就转化成了,寻找左边(或右边)最近的比它大的元素。

自然而然,我们应该使用单调递减栈(严格单调递减):每遍历到一个元素的时候,不断出栈直到栈空或者栈顶元素大于该元素。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function trap(height: number[]): number {
let n = height.length;
let left = new Array(n);
let right = new Array(n);

let s = [];

for (let i = 0; i < n; ++i) {
// 出栈,直到栈空或者栈顶元素>该元素
while (s.length != 0 && height[s[s.length - 1]] <= height[i]) {
s.pop();
}
if (s.length == 0) {
left[i] = height[i];
}
else {
left[i] = left[s[s.length - 1]];
}
s.push(i);
}
s.splice(0, s.length);
for (let i = n - 1; i >= 0; --i) {
// 出栈,直到栈空或者栈顶元素>该元素
while (s.length != 0 && height[s[s.length - 1]] <= height[i]) {
s.pop();
}
if (s.length == 0) {
right[i] = height[i];
}
else {
right[i] = right[s[s.length - 1]];
}
s.push(i);
}
let ans = 0;
for (let i = 0; i < n; ++i) {
ans += Math.min(left[i], right[i]) - height[i];
}

return ans;
};