单调栈 - 直方图最大矩形面积 (保姆级讲解)

389 阅读4分钟

前言

本人只是一个菜鸟前端, 出于兴(内)趣(卷)有时会刷一些算法题. 这天在刷b站的时候看到了讲解单调栈的视频, 就是用这道题作为讲解的例子, 在了解了其大概思路的情况下, 完成了这道题.

再次声明, 本人只是一个菜鸟前端且语言表达能力极差(😭), 说的不对的情况请大佬指出鸭~

贴一下力扣对应链接: leetcode.cn/problems/la…

解题前分析

要计算最大矩形面积,那么我们就可以遍历数组,然后计算每个元素的能取到的最大矩形面积就可以了。

当前元素的最大矩形面积 = 当前元素的高 * 宽度, 这个宽度就是下一个小于当前元素到上一个小于当前元素的距离。

矩形面积示例图

如图所示, 最大的面积就是 10 ,也就是 5 * 2.

当然, 我们肯定不能直接搜索 上一个和下一个小于当前元素的索引,所以就可以使用单调栈来解决问题.

单调栈

单调栈,顾名思义就是单调递增或者单调递减的栈。

这道题需要使用单调递增的栈,这样可以保证 上一个元素一定是小于的当前元素的,并且在入栈时会进行比较得到 下一个小于当前元素的. 这样就可以顺利得到 上一个和下一个小于当前元素的索引

不能使用单调递减的单调栈,因为只能保证上一个元素更大,但是我们需要找到更小的.

解题思路

遍历 heights 数组,按照单调递增的规律进行入栈,使用 [2, 1, 5, 6, 2, 3] 进行演示:

  1. 遍历遇到 2 直接入栈, [2]
  2. 遍历遇到 1,1 小于 2 所以 2 出栈,1进栈,并且计算以 2 为高的矩形面积,此时自然为 2 * 1 = 2, [1]
  3. 遍历 5 进栈 [1, 5]
  4. 6 进栈 [1, 5, 6]
  5. 遍历 2; 6 出栈,并且计算面积 6 * 1 = 6, 5 再出栈 area = 5 * 2 = 10; 然后 2 进栈 [1, 2]
  6. 遍历 3 进栈, 结果为 [1, 2, 3]
  7. 因为需要每个元素对应的面积值, 所以还需要遍历栈, 计算面积, 最后得出最大值.

注意:代码中栈里面的应该是索引值这里使用元素值,是为了方便

代码实现

function largestRectangleArea(heights: number[]): number {
  // *单调栈 注意单调栈里面存储的是索引值  因为有索引值可以直接获得高度,但是有高度却不能直接得到索引
  const stack: number[] = [];
  let max = 0;
  for (let i = 0; i < heights.length; i++) {
    const cnt = heights[i];

    // *cnt <= 栈顶对应元素值 那么就应该将栈顶出栈
    // *并且使用 while 循环,确保每一个 >= cnt 的索引都出栈
    // !重点,为什么这里需要小于等于 ; 
    // !假设当前数组为 [1, 2, 2, 2, 1] 中间三个相同的 2 
    // !那么这三个 2 得出的矩形面积应该都是相等的,所以我们只需要保存最后一个就可以了
    while (stack.length && cnt <= heights[stack[stack.length - 1]]) {
      const head = stack.pop() as number;// 将栈顶 pop 出来
      // *如果上一个元素与当前元素相等,那么不需要进行计算
      if (cnt === heights[i - 1]) continue;
      // *因为我们上面将相等的情况剔除了, 当前最后一个元素一定是小于栈顶的
      // *因为上面使用的是 pop 所以如果此时 stack 为空,
      // *那么说明栈顶对应的值是最小的, 那么距离应该是 左边的总长度 所以需要将 prev 赋值为 -1
      const prev = stack[stack.length - 1] ?? -1;
      // !width 应该是下一个小于到上一个小于之间的距离,且是不含这两个小于的。 
      // !所以 (i - 1) 就是 cnt 的上一个索引值 - prev 就是 **之间的距离**。
      // !注意这里是 i - 1 不是栈顶,因为这是一个 while 循环,栈顶永远是自身,而不是下一个小于自己的元素。
      // !那么为什么 prev 在 stack 为空的情况是 -1 呢,
      // !因为数组以 0 为开头,假设当前数组为:[2, 1] 那么遍历到 1 时,2 会出栈,i - 1 = 0 ,prev 只有为 -1。
      const width = i - 1 - prev;
      max = Math.max(max, (heights[head] * width));
    }
    // *进栈
    stack.push(i);
  }

  let maxIndex = -1;
  // *如果栈内还有索引,那么就要计算每个索引对应值的矩形面积  计算方法与上面差不多
  while (stack.length) {
    const head = stack.pop() as number;
    const height = heights[head];
    const prev = stack[stack.length - 1] ?? -1;
    if (maxIndex === -1) {
      maxIndex = head;
    }
    const width = maxIndex - prev;

    max = Math.max(max, height * width);
  }
  return max;
};

执行结果

使用这种单调栈解法的时间复杂度和空间复杂度都是 O(N)

代码还是有一些可以优化的地方, 力扣题解中就有详细的说明.