「前端刷题」84. 柱状图中最大的矩形

123 阅读2分钟

「这是我参与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 <=105
  • 0 <= 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
}