题目链接: 84. 柱状图中最大的矩形
一、题目描述:
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例 1:
输入: heights = [2,1,5,6,2,3]
输出: 10
解释: 最大的矩形为图中红色区域,面积为 10
示例 2:
输入: heights = [2,4]
输出: 4
提示:
1 <= heights.length <=1050 <= heights[i] <= 104
题目模版
/**
* @param {number[]} heights
* @return {number}
*/
var largestRectangleArea = function(heights) {
};
思路分析
这道题可以用两个 for 循环做一个暴力求解
function largestRectangleArea(heights) {
const n = heights.length;
let ans = 0;
for (let left = 0; left < n; ++left) {
let minHeight = Infinity;
for (let right = left; right < n; ++right) {
minHeight = Math.min(minHeight, heights[right]);
ans = Math.max(ans, (right - left + 1) * minHeight);
}
}
return ans;
}
其示例如下
GPT-6.1 Sol 出的这个 Visualize,我看完直接瘫坐在椅子上,久久不能平静。
我只是贴了一段算法,它就把运行过程做成了能播放、暂停、逐步查看的交互动画。
这一刻,代码真的在我眼前活过来了。 --- codex
暴力解法的复杂度是 O(N^2),空间复杂度是 O(1),但其实还有另一种思路,就是从单个柱子的左右两边循环查找
它的一个查找思路就是:
- 在左边找到最近并且比它矮的柱子
- 在右边找到最近并且比它矮的柱子
那么以这个这个柱子能够辐射的范围/边界就确定了,因为最近两个比它矮的柱子,把它的柱子复制粘贴挡住了,以 index: 2 的柱子举例
学过高数的同学们肯定知道了,这就像夹逼定理
纯属虚构:本题采用暴力枚举,与夹逼定理无关
还有些小伙伴又有疑问,你这样夹逼,万一我宽度取胜呢?比如
但是又要注意了,我们的单柱+左右扩散是每个柱子都会去计算,所以我们还是能够知道面积为 12 的柱子最大
既然思路已经确定,那么我们该怎么实现呢?我们复制粘贴一下上面说的过程
- 在左边找到最近并且比它矮的柱子
- 在右边找到最近并且比它矮的柱子
其实这是单调栈对应的case,空间换时间,以「在右边找到最近并且比它矮的柱子」为例,我们用一个mono_stack存一个单调递增的栈,当在curr_i的找「在右边找到最近并且比它矮的柱子」,可以去遍历mono_stack里找到,算法如下
const r = new Array(heights.length);
mono_stack = [];
for(let i = heights.length - 1; i >= 0; i--) {
while(mono_stack.length && heights[mono_stack[mono_stack.length - 1]] >= height[i]) mono_stack.pop();
// 三元表达式的两种 case 可见紧跟代码块的示例图片
r[i] = !mono_stack.length ? heights.length : mono_stack[mono_stack.length - 1];
// 中止 while 循环的条件必然是 mono_stack[mono_stack.length - 1]
// 即 栈顶高度 < heightByCurrI
// 因此根据我们单调栈的单调递增的定义,此时需要入栈
mono_stack.push(i);
}
这个时候又有同学有疑问了,你整个所谓的单调栈,也是 for 里面套个循环(即 while),它的复杂度不还是 O(N^2)?
其实不是,举个例子
无论是 [3,2,1] 还是 [2,1,2,1] 这种极端 case,它的遍历次数都是 <= n,也就是一个常数范围内,在「在右边找到最近并且比它矮的柱子」的遍历里,它的最差循环次数是 O(N + N) -> O(2*N) -> O(N)
在思路确定以后,就可以得到我们的 AC 代码,如下
AC 代码
/**
* @param {number[]} heights
* @return {number}
*/
var largestRectangleArea = function (heights) {
let mono_stack = [];
const l = new Array(heights.length);
for (let i = 0; i < heights.length; i++) {
while (mono_stack.length && heights[mono_stack[mono_stack.length - 1]] >= heights[i]) mono_stack.pop();
l[i] = mono_stack.length === 0 ? -1 : mono_stack[mono_stack.length - 1];
mono_stack.push(i);
}
mono_stack = [];
const r = new Array(heights.length);
for (let i = heights.length - 1; i >= 0; i--) {
while (mono_stack.length && heights[mono_stack[mono_stack.length - 1]] >= heights[i]) mono_stack.pop();
r[i] = mono_stack.length === 0 ? heights.length : mono_stack[mono_stack.length - 1];
mono_stack.push(i);
}
let ret = 0;
for (let i = 0; i < heights.length; i++) {
ret = Math.max(ret, (r[i] - l[i] - 1) * heights[i]);
}
return ret;
};