「这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战」。
题目
链接:leetcode-cn.com/problems/la…
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例 1:
**输入:**heights = [2,1,5,6,2,3] **输出:**10 **解释:**最大的矩形为图中红色区域,面积为 10
示例 2:
输入: heights = [2,4] 输出: 4
提示:
1 <= heights.length <=1050 <= heights[i] <= 104
解题思路
名词释义
- 高个阵营: 从左到右审视直方图,在没遇到下一个bar之前,就存在已有长方形,比较高的就是“高个阵营”
- 矮个阵营: 和高个阵营相反
高矮阵营各有优势
- 遇到一个新的 bar 时
- 高个阵营兼容不了它,还因为矮 bar 的 “阻隔”,自此面积停止变大
- 矮个阵营可以兼容它,长度因此变长,面积变大
- 矮个阵营矮,但兼容性好,通过长度变长,变得更强
- 高个阵营高,但兼容性差,遇高则强,遇矮则生长停滞
高 bar 矮 bar 所带来的不同意义
- 来了个高 bar ,两个阵营都能补强。此时不需要计算面积,因为面积在变大,没有到最大
- 来了个矮 bar ,高个阵营面积不能再变大了,矮个阵营有逆袭的可能。考察高个阵营已无意义,算完面积即可抛弃,继续观察矮个阵营
问题来了,高矮是相对的
- 没有绝对的高 bar ,也没有绝对的矮 bar,没有绝对的高个阵营,也没有绝对的矮个阵营
- 难道每次都要根据一个新 bar 去区分高矮阵营吗?显然不实际
高矮,是通过比较产生的
- 举个例子:小王,军训集合迟到了,姗姗来迟
- 如果此时队伍参差不齐,很难比较出谁比小王高,谁比小王矮
- 如果队伍按高矮排好,小王从队头一个个地比,大家很容易知道自己和小王谁高
- 小王也很容易就知道自己该排到哪里
让比较的过程更快
- 将高矮排好(维护一个单调序列),迎接新 bar 的到来
- 问题来了,维护单调栈,还是单调队列?单调递增,还是单调递减?
为什么是单调递增?为什么是栈?
- 如果是单调递减栈
- 新 bar 比栈顶矮,入栈,维持递减性
- 新 bar 比栈顶高,靠近栈顶的矮个阵营会补强,继续考察,排后面的高个停止生长,需要抛弃
- 可是高个阵营不在栈顶,不好出栈啊
- 那,队列可以吗?让高个从队尾出列?
- 好的,但是,人家新 bar 要进来啊,要从高个出列的地方入列,才能保持单调性,这就不是队列了,是栈了
- 所以,只能是单调递增栈
- 新 bar 比栈顶高,入栈,保持单增性
- 新 bar 比栈顶低,栈顶的高个阵营停止生长,出栈,后头的矮个阵营留待观察
栈的单调性,谁去谁留,变得清晰
- 高个阵容遇到矮 bar 而停止发育,计算它形成的长方形面积后,就出栈
- 栈是单调递增的,不断将新的栈顶 bar 和当前 bar 比较,高的就走
- 等到栈顶 bar 不再高于当前 bar,就不再出栈,并让当前 bar 入栈
单调栈记录什么?
- bar 的位置(索引),高度通过
height[i]求出,宽度通过索引相减求出
捋一下整个过程
- 维护一个 stack 栈。遍历
heights数组的每一个 bar - 当前 bar 比栈顶的 bar 高,直接入栈
- 当前 bar 比栈顶的 bar 矮:
- 栈顶元素(索引)出栈,暂存给
stackTopIndex变量 - 计算以
heights[stackTopIndex]为高的长方形的面积,宽度 = 当前 bar 的索引 i - 新的栈顶索引 - 1 ,与全局的最大比较
- 栈顶元素(索引)出栈,暂存给
- 当前 bar 继续和新的栈顶比较,重复上面过程,直到当前 bar 不再比栈顶的 bar 矮,入栈
下面是边界情况的分析
栈空了,面积公式就没法用了
- 求长方形的宽度,需要新的栈顶,如果没有呢?当栈只有一个元素,栈顶出栈,栈就空了
- 我们再思考另一个问题:让 heights 数组的索引 0 入栈,依据是什么?
- 入栈的依据是当前 bar 比栈顶 bar 高。问题是现在没有栈顶可以比较
- 我们可以设立一个高为 0 的虚拟 bar ,放在 heights 的 0 位置,它不影响结果,却可以让第一条 bar 的索引,名正言顺地入栈
- 同时解决了第一个问题:不会有别的 bar 比它更矮了,因此该 bar 永不出栈
最后一个 bar 需要解救
- 最后一个 bar 不会遇到新 bar 了,如果它在栈中,那就没有机会出栈了,意味着,没有机会计算栈中的长方形面积了
- 我们设立一个虚拟的高为 0 的 bar,放在 heights 数组的最右,栈中的 bar 都比它高,能一一出栈,得到解救
const largestRectangleArea = (heights) => {
let maxArea = 0
const stack = []
heights = [0, ...heights, 0]
for (let i = 0; i < heights.length; i++) {
while (heights[i] < heights[stack[stack.length - 1]]) { // 当前bar比栈顶bar矮
const stackTopIndex = stack.pop() // 栈顶元素出栈,并保存栈顶bar的索引
maxArea = Math.max( // 计算面积,并挑战最大面积
maxArea, // 计算出栈的bar形成的长方形面积
heights[stackTopIndex] * (i - stack[stack.length - 1] - 1)
)
}
stack.push(i) // 当前bar比栈顶bar高了,入栈
}
return maxArea
}