一、题目描述
给定 n
个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1
。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
以上是柱状图的示例,其中每个柱子的宽度为 1
,给定的高度为 [2,1,5,6,2,3]
。
图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10
个单位。
示例:
输入: [2,1,5,6,2,3]
输出: 10
二、思路分析
木桶原理吧,以最短那块木板为中心。
中心扩展法
看到这个题第一个想法是用“中心扩展法”,循环遍历,以第i
个元素为中心,往左右两边找,找到比当前元素小的就终止。这个办法能找到每个元素自己可以计算的最大面积,再一比较,整个矩形的最大面积就出来了
左边那个小于i
的是left
;右边那个小于i
的是right
;所以有right - left - 1
个大于等于第i
个元素高度的矩形
则第i
个元素最大面积的计算是:heights[i] * (right - left - 1)
var largestRectangleArea = function (heights) {
if (heights.length <= 0) return 0;
let max = 0;
// 加这一行:考虑到只有一个元素([1]),或者所有元素都相同时([1, 1, 1])会出错
heights = [0, ...heights, 0];
for (let i = 0; i < heights.length; i++) {
let left = i, right = i;
// 往左找,找到第一个小于i的
while (left > 0 && heights[left] >= heights[i]) {
left--;
}
// 往右找,找到第一个小于i的
while (right < heights.length && heights[right] >= heights[i]) {
right++;
}
max = Math.max(max, heights[i] * (right - left - 1));
// console.log(i, left, right, max);
}
return max;
};
我觉得这个解法还可以的,可惜超出时间限制了,时间复杂度是O(n^2)
;没办法,只能继续优化或者找其它的解了,算法的魅力不就是在这里吗?
单调栈
单调栈这个解法是看了其它大佬的解法才搞出来的,是利用了栈后进先出的特性。
单调栈的意思是栈中元素按递增顺序或者递减顺序排列;最大好处就是时间复杂度是线性的,每个元素只遍历一次!
不过这个解法的核心还是不变的,是为了在一次遍历的时候找到当前元素左右两边第一个比它小的元素,又或者说,找到以当前元素为最短木板的后左右两边都比它高或者相等的所有元素。
话不多说,先上代码。
三、AC代码
var largestRectangleArea = function (heights) {
if (heights.length <= 0) return 0;
let max = 0;
// 加这一行:考虑到只有一个元素([1]),或者所有元素都相同时([1, 1, 1])会出错
heights = [0, ...heights, 0];
// console.log(heights);
// 用栈来记录数组元素的下标
let stack = [];
for (let i = 0; i < heights.length; i++) {
// 当前元素和栈顶元素比较,如果当前元素小于栈顶元素,则栈顶元素出栈
// 并求以出栈元素为中心时,它的最大面积
while (heights[i] < heights[stack[stack.length - 1]]) {
let index = stack.pop(); // 取出栈顶元素
// 以出栈元素为中心求面积:当前元素(i)与栈顶元素(stack[stack.length - 1])之间都是大于等于栈顶元素的值
// 换句话说就是:当前元素(i)和栈顶元素(stack[stack.length - 1])是左右两边第一个比出栈元素(index)小的值,
// 即i就是right,stack[stack.length - 1]就是left
let tempArea = heights[index] * (i - stack[stack.length - 1] - 1);
// console.log(`当前元素:${i}。出栈元素:下标:${index},值:${heights[index]},面积:${tempArea}`);
max = Math.max(max, tempArea);
}
// 当前元素大于栈顶元素或栈没有元素时,入栈
stack.push(i);
// console.log('下标栈stack:', stack);
}
return max;
};
看不懂代码的可以移除注释看一下出栈入栈的过程~
然后这里也再解释一下:
单调栈的结构保证栈顶元素是最大的,当遍历到比它小的就意味着它要出栈(出栈意味着要计算它为中心时的面积了)了;即这时候的right
就是i
,那left
就是栈顶元素底下那个了,即原栈顶元素出栈后的栈顶元素stack[stack.length - 1]
。
然后还有一个保障就是,数组前后都加了个0
,这样原数组所有的元素都是会入栈出栈的,这样就可以计算以每一个元素为中心时的面积了。
开始遍历(这里出栈入栈的是元素的下标):
0
入栈;
2
比0
大,入栈;
到1
,比2
小,2
要出栈,计算以2
为中心时的面积;2
出栈后1
比0
大,1
入栈;
到5
,比1
大,入栈;
到6
,比5
大,入栈;
到2
,比6
小,6
要出栈,计算以6
为中心时的面积;6
出栈后,栈顶元素5
还是比2
大,5
要出栈,计算以5
为中心时的面积;5
出栈后,2
比1
大,2
入栈;
到3
,比2
大,入栈;
到0
,比3
小,3
要出栈,计算以3
为中心时的面积;3
出栈后,栈顶元素2
还是比0
大,2
要出栈,计算以2
为中心时的面积;2
出栈后,栈顶元素1
还是比0
大,1
要出栈,计算以1
为中心时的面积;之后0
等于 0
;
遍历结束
四、总结
单调栈真不错,不过要理解的话还是自己敲一遍代码,打印出执行过程,写一个题解要深刻一些,又get一个新思路,一个新解法,开心!!!哈哈哈,大笑三声
我的题解希望对你有所帮助,有错的地方还望指出,万分感谢!