文章目录

  • 84. 柱状图中最大的矩形
  • 解题
  • 方法一:单调栈
  • 方法二:单调栈+优化


84. 柱状图中最大的矩形

84. 柱状图中最大的矩形

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

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

示例 1:

【NO.36】LeetCode HOT 100—84. 柱状图中最大的矩形_柱状图

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

示例 2:

【NO.36】LeetCode HOT 100—84. 柱状图中最大的矩形_leetcode_02

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

提示:

1 <= heights.length <=10^5
0 <= heights[i] <= 10^4

解题

方法一:单调栈

遍历每个高度,是要以当前高度为基准,寻找最大的宽度:组成最大的矩形面积那就是要找左边第一个小于当前高度的下标left,再找右边第一个小于当前高度的下标right 那宽度就是这两个下标之间的距离了,但是要排除这两个下标,所以是right-left-1,用单调栈就可以很方便确定这两个边界了

单调栈,从栈底到栈顶递增的,当前数比栈顶小时,要弹出栈顶,循环对比,最后栈顶元素就是第一个小于当前高度的下标(因为我们要找的第一个小于当前高度的下标)

// 时间复杂度,空间复杂度都为O(n)
class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        // 记录左边第一个小于当前高度的下标left
        int[] left = new int[n];
        // 记录右边第一个小于当前高度的下标right
        int[] right = new int[n];
        
        // 单调栈,底到顶 下标所对应的 数递增
        Deque<Integer> stack = new ArrayDeque<>();

        for (int i = 0; i < n; i++) {
            // 如果 当前元素比 栈顶下标对应元素 小,则弹出栈顶
            while (!stack.isEmpty() && heights[i] <= heights[stack.peek()]) {
                // 将栈顶元素弹出
                stack.pop();
            }
            // 栈顶元素就是第一个小于当前高度的下标
            left[i] = stack.isEmpty() ? -1 : stack.peek();
            // 入栈
            stack.push(i);
        }

        stack.clear();
        for (int i = n-1; i >= 0; i--) {
            while (!stack.isEmpty() && heights[i] <= heights[stack.peek()]) {
                // 将栈顶元素弹出
                stack.pop();
            }
            // 栈顶元素就是第一个小于当前高度的下标
            right[i] = stack.isEmpty() ? n : stack.peek();
            // 入栈
            stack.push(i);
        }

        // 求面积
        int ans = 0;
        for (int i = 0; i < n; i++) {
            ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
        }

        return ans;

    }
}

方法二:单调栈+优化

在方法一中,我们首先从左往右对数组进行遍历,借助单调栈求出了每根柱子的左边界,随后从右往左对数组进行遍历,借助单调栈求出了每根柱子的右边界。那么我们是否可以只遍历一次就求出答案呢?

答案是可以的。在方法一中,我们在对位置 i 进行入栈操作时,确定了它的左边界。从直觉上来说,与之对应的我们在对位置 i 进行出栈操作时可以确定它的右边界!仔细想一想,这确实是对的。当位置 i 被弹出栈时,说明此时遍历到的位置 i0的高度小于等于 height[i] 【注意:i < i0】,并且在 i0与 i 之间没有其他高度小于等于 height[i] 的柱子。这是因为,如果在 i 和 i0之间还有其它位置的高度小于等于 height[i]的,那么在遍历到那个位置的时候,i 应该已经被弹出栈了。所以位置 i0就是位置 i 的右边界。

等等,我们需要的是「一根柱子的左侧且最近的小于其高度的柱子」,但这里我们求的是小于等于,那么会造成什么影响呢?答案是:我们确实无法求出正确的右边界,但对最终的答案没有任何影响。这是因为在答案对应的矩形中,如果有若干个柱子的高度都等于矩形的高度,那么最右侧的那根柱子是可以求出正确的右边界的,而我们没有对求出左边界的算法进行任何改动,因此最终的答案还是可以从最右侧的与矩形高度相同的柱子求得的。

class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        // 记录左边第一个小于当前高度的下标left
        int[] left = new int[n];
        // 记录右边第一个小于当前高度的下标right
        int[] right = new int[n];
        Arrays.fill(right, n);

        // 栈
        Deque<Integer> stack = new ArrayDeque<Integer>();
        for (int i = 0; i < n; i++) {
            while (!stack.isEmpty() && heights[i] <= heights[stack.peek()]) {
                //在确认左边的left时,同时也可以确认右边right
                right[stack.peek()] = i;
                // 比当前数大的弹出
                stack.pop();
            }
            // left就是 栈顶元素
            left[i] = (stack.isEmpty() ? -1 : stack.peek());
            // 入栈
            stack.push(i);
        }

        // 统计面积
        int ans = 0;
        for (int i = 0; i < n; i++) {
            ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
        }

        return ans;
    }
}