力扣第八十四题-柱状图中最大的矩形

366 阅读3分钟

「这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战

前言

力扣第八十四题 柱状图中最大的矩形 如下所示:

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

示例 1:

输入: heights = [2,1,5,6,2,3]
输出: 10
解释: 最大的矩形为图中红色区域,面积为 10

示例 2:

输入: heights = [2,4]
输出: 4

一、思路

题目的意思很简单,就是找到柱状图中最大的矩形面积

这一题最简单就是直接暴力求解了,因为在柱状图中每一个高都有一个对应的面积,只需要将以 heights[i]作为高的矩形面积都计算出来后,取一个最大值即可

我们以 heights = [2,1,5,6,2,3] 举个例子,下图就是以 heights[4] 作为高的矩形面积,它的面积为 2 x 4 = 8

image.png

我们发现以 height[i] 作为高的矩形有这个规律:左边界的高度不小于 heights[i],右边界的高度也不小于 heights[i]

利用这个规律我们可以解决这题了,大致的步骤如下所示:

  1. 找到以 height[i] 作为高的的矩形的左右边界
  2. 计算出每一个矩形的面积,取其中的最大值

而求 heights[i] 的左右边界,我们可以使用单调栈,保持栈顶始终存储最靠近的最小值

图解算法(单调栈)

此处以从左到右,求以 heights[i] 作为高的左边界。我们在栈中只存储元素的下标,因为高度可以通过下标来找到

注:为了贴近实际情况,我们的左右边界区间使用的闭合的,即 [i, j]。如 [0, 0] 表示左右边界都为下标 0

  1. 我们知道第一个元素的左边界一定是子集,所以栈顶初始为 0,首元素的左边界也为 0

image.png

  1. 从第二个元素开始遍历,此时栈顶下标对应的高度 height[0] > 1 故出栈,再更新左边界,最后将当前的下标入栈

image.png

  1. 遍历到第三个元素,此时栈顶下标对应的高度 height[1] < 5 不出栈,更新左边界,最后将当前的下标入栈

image.png

  1. 遍历到第四个元素,此时栈顶下标对应的高度 height[2] < 6 不出栈,更新左边界,最后将当前的下标入栈

image.png

  1. 遍历到第五个元素,此时栈顶下标对应的高度 height[3] > 2 出栈,直到栈顶下标对应的高度小于 2,再更新左边界,最后将当前的下标入栈

image.png

  1. 遍历到第六个元素,此时栈顶下标对应的高度 height[4] < 3 不出栈,更新左边界,最后将当前的下标入栈

image.png

  1. 至此就完成了每个高度的左边界寻找了,右边界也是同样的道理。

二、实现

实现代码

实现代码与思路中保持一致

    /**
     * 单调栈
     */
    public int largestRectangleArea(int[] heights) {
        int ret = 0;
        int len = heights.length;
        int[] left = new int[len];
        int[] right = new int[len];
        // 找到以 heights[i] 作为高度的左边界,栈中存储下标
        Deque<Integer> stack = new ArrayDeque<>();
        stack.push(0);
        left[0] = 0;
        for (int i=1; i<len; i++){
            while (!stack.isEmpty() && heights[stack.peek()] >= heights[i] ) {
                stack.pop();    // 只要栈中下标对应的高度大,就一直出栈
            }
            if (stack.isEmpty()) {  // 如果栈为空,说明左边界是第一个元素
                left[i] = 0;
            } else {
                left[i] = stack.peek() + 1;
            }
            stack.push(i);
        }
        stack.clear();
        stack.push(len-1);
        right[len-1] = len-1;
        // 找到以 heights[i] 作为高度的右边界,栈中存储下标
        for (int i=len-1; i>-1; i--) {
            while (!stack.isEmpty() && heights[stack.peek()] >= heights[i]) {
                stack.pop();
            }
            if (stack.isEmpty()) {  // 如果栈为空,说明左边界就是自身
                right[i] = len-1;
            } else {
                right[i] = stack.peek() - 1;
            }
            stack.push(i);
        }
        // 计算面积
        for (int i=0; i<len; i++) {
            ret = Math.max(ret, (right[i] - left[i] + 1) * heights[i]);
        }
        return ret;
    }

测试代码

    public static void main(String[] args) {
        int[] heights = {2, 1, 5, 6, 2, 3};
        new Number84().largestRectangleArea(heights);
    }

结果

image.png

三、总结

感谢看到最后,非常荣幸能够帮助到你~♥

如果你觉得我写的还不错的话,不妨给我点个赞吧!如有疑问,也可评论区见~