leetcode
leetcode 2951 ~ 3000
柱状图中最大的矩形

柱状图中最大的矩形

难度:

标签:

题目描述

English description is not available for the problem. Please switch to Chinese.

代码结果

运行时间: 122 ms, 内存: 26.3 MB


/*
 * 思路:Java Stream 不太适合处理需要状态维护的单调栈问题。
 * 但是我们可以利用数组索引流和叠加来进行模拟。
 */

import java.util.Stack;
import java.util.stream.IntStream;

public class Solution {
    public int largestRectangleArea(int[] heights) {
        Stack<Integer> stack = new Stack<>();
        final int[] maxArea = {0};
        IntStream.rangeClosed(0, heights.length).forEach(i -> {
            int h = (i == heights.length ? 0 : heights[i]);
            while (!stack.isEmpty() && h < heights[stack.peek()]) {
                int height = heights[stack.pop()];
                int width = stack.isEmpty() ? i : i - 1 - stack.peek();
                maxArea[0] = Math.max(maxArea[0], height * width);
            }
            stack.push(i);
        });
        return maxArea[0];
    }
}

解释

方法:

本题解采用单调栈的方法来解决问题。首先,在数组的开始和结束位置分别添加一个高度为0的柱子,以便处理边界条件。维护一个单调递增的栈,栈中存储的是柱子的索引。遍历输入数组中的每个柱子,当当前柱子的高度大于栈顶元素对应的柱子高度时,直接将当前索引入栈。如果相等,替换栈顶元素。如果当前柱子的高度小于栈顶元素对应的柱子的高度,则持续将栈顶元素弹出,并计算以该栈顶元素为高度的矩形面积,直到栈顶元素的高度小于或等于当前柱子的高度。计算矩形的宽度为当前索引与新的栈顶元素的索引之间的差减一。每次计算完面积后,更新最大面积。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么在解题思路中需要在数组的开始和结束位置分别添加一个高度为0的柱子?这样做有什么特殊的作用?
在数组的开始和结束位置添加高度为0的柱子主要是为了简化边界条件的处理。这样做确保了无论柱状图的高度如何变化,所有的柱子都可以在遍历结束时被正确地处理。具体来说,添加在开始位置的0高度柱子保证了栈的初始化状态,而结尾位置的0高度柱子则确保最后一个真正的柱子可以被计算(因为任何大于0的高度都会被弹出处理)。这两个边界柱子作为哨兵,简化了代码逻辑,避免了在循环中对栈空状态的额外检查。
🦆
单调栈中为什么选择维护一个单调递增的栈而不是单调递减的栈?这样的选择对算法的效果有何影响?
单调递增栈的选择允许我们在遍历时,一旦遇到比栈顶元素小的柱子高度,就可以立即计算以栈顶元素为高度的最大矩形面积。因为在单调递增栈中,每个元素的左边界是确定的,即其前一个元素。这样,当某个高度弹出栈时,我们可以确定这个高度的有效宽度,从而快速计算出以该高度为顶的最大矩形面积。如果使用单调递减栈,则在弹栈时难以确定左边界,因此会增加解题的复杂度。
🦆
在遍历过程中,如果当前柱子高度等于栈顶柱子高度时,为什么要替换栈顶元素而不是保留原栈顶元素?
当当前柱子的高度等于栈顶柱子的高度时,替换栈顶元素的目的是更新可能的最长宽度的起始位置。如果保留原栈顶元素,那么之后计算宽度时会使用错误的起始索引(即更早的索引),这可能导致宽度被错误地延伸,从而计算出错误的面积。替换成当前的索引能确保,如果后续有相同高度的柱子继续处理,它们的宽度计算是基于最新的、正确的起始位置。

相关问题