这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战
柱状图中最大的矩形(题号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
链接
解释
这题啊,这题是根本想不到。
说实话,看到这题的时候第一感觉就是DP,用dp[i][j]来表示从i到j区间内的最低高度,确实这种思路是可行的,但是会超时,因为需要两次for循环,时间复杂度为O(nlogn),具体的内容会放到答案中进行讲解,这里主要介绍下可以AC的官方解法。
让我们以官方的第一个栗子为栗子,来好好分析下,再看下这张图👇:
如果要取到最大面积的内容,则需要找到一个N列连续的列,这些列的左侧和右侧必须比这个连续列的边界都小,在这个栗子中,连续的列是5和6,而左侧的1和右侧的2都比边界小,如果扩展到1的话,那么这个连续区间的面积就是3 * 1,也就是3,显然比5 * 2小。
从上面的推导中可以得出一个结论,一个区间的最大值是由两个边界值决定的,好像说了跟没说一样,但其实关键点就在这里,这里从2到1,可以发现面积是在变小的,因为边界值变小了,所以此时应该计算下当前的最大面积,可以得到2,那从6变成2,面积同样也变小了,那么此时应该计算前面的最大矩阵值,得到10,后续也可以继续进行操作,遇到比边界值大的就不做统计,遇到边界值比自己小的,就开始计算前面的最大面积。
那最大面积计算到哪一步呢?计算到当前值比前面值大的那个值,比方说我们遇到了6后面的2,那么前面值中比2大的是1,所以当前统计的面积应该就是5和6两根柱子的最大面积,整体逻辑就是这样的。
那该如何解决最开始和最后两个柱子的最大值呢?我们完全可以在开始和结束位置的边缘添加两根高度为0的柱子,此时不仅可以统计到最开始的那根柱子,最后一根柱子也会因为后面还有柱子比自己的高度更低而经历一次计算。
整体思路就是这样,下面看看代码。
自己的方法(遍历)
整体逻辑和解释中说的比较类似,代码👇:
var largestRectangleArea = function(heights) {
const len = heights.length
let active = null
let res = 0
for (let i = 0; i < len; i++) {
res = Math.max(res, heights[i])
for (let j = i; j < len; j++) {
if (i === j) {
active = heights[i]
} else {
active = Math.min(active, heights[j])
res = Math.max((j - i + 1) * active, res)
}
}
}
return res
};
这里直接看最后优化后的结果,利用active来存储上次的结果,也就是i-1, j的最小高度,但这种方法由于时间复杂度较高,最后跑到第92的测试用例的时候GG了。
由于没有通过,这里就不细说了。
更好的方法(栈)
在解释中说过,我们需要记录当前柱子的高度和之前柱子的高度,那么显然,栈是比较合理的一种数据结构。
每当走到一个新柱子的时候,判断一下这个柱子的高度是否比栈顶柱子更高,如果更高,直接推入栈,成为新的栈顶元素;如果比栈顶元素更低,那么去掉当前的栈顶元素,开始计算栈顶元素的最大面积。
注意,去掉栈顶元素的操作是一个while循环操作,因为有可能去掉之后,新的栈顶元素还是比当前高度更高,所以需要进行一个持续性的操作,并且每次操作后重新计算可能出现的最大面积,每操作一次后,根据index,可以使得宽度加1。
👇就是代码了:
function largestRectangleArea(heights) {
heights = [0, ...heights, 0]
let res = 0
const stack = []
for (let i = 0; i < heights.length; i++) {
while (heights[i] < heights[stack[stack.length - 1]]) {
const activeHeight = stack.pop()
res = Math.max(
res,
heights[activeHeight] * (i - stack[stack.length - 1] - 1)
)
}
stack.push(i)
}
return res
}
这块整体逻辑比较绕,实在无法理解可以打几个log看看。
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇