单调栈题目:柱状图中最大的矩形

题目

标题和出处

标题:柱状图中最大的矩形

出处:84. 柱状图中最大的矩形

难度

7 级

题目描述

要求

给定整数数组 heights \texttt{heights} heights 表示柱状图中各个柱子的高度,其中每个柱子的宽度为 1 \texttt{1} 1,返回在该柱状图中的矩形的最大面积。

示例

示例 1:

示例 1

输入: heights   =   [2,1,5,6,2,3] \texttt{heights = [2,1,5,6,2,3]} heights = [2,1,5,6,2,3]
输出: 10 \texttt{10} 10
解释:上图为一个柱状图,其中每个柱子的宽度为 1 \texttt{1} 1。最大的矩形为图中红色区域,面积为 10 \texttt{10} 10

示例 2:

示例 2

输入: heights   =   [2,4] \texttt{heights = [2,4]} heights = [2,4]
输出: 4 \texttt{4} 4

数据范围

  • 1 ≤ heights.length ≤ 10 5 \texttt{1} \le \texttt{heights.length} \le \texttt{10}^\texttt{5} 1heights.length105
  • 0 ≤ heights[i] ≤ 10 4 \texttt{0} \le \texttt{heights[i]} \le \texttt{10}^\texttt{4} 0heights[i]104

解法

思路和算法

为了得到柱状图中最大的矩形,对于下标 i i i,需要找到最大的下标 j j j 和最小的下标 k k k,满足 j < i < k j < i < k j<i<k heights [ j ] < heights [ i ] \textit{heights}[j] < \textit{heights}[i] heights[j]<heights[i] heights [ k ] < heights [ i ] \textit{heights}[k] < \textit{heights}[i] heights[k]<heights[i],则存在一个宽度为 k − j − 1 k - j - 1 kj1、高度为 heights [ i ] \textit{heights}[i] heights[i] 的矩形,计算该矩形的面积并更新矩形的最大面积。

直观的做法是遍历柱状图中的每个柱子,对于每个下标 i i i,向两边遍历寻找对应的下标 j j j k k k,得到以 heights [ i ] \textit{heights}[i] heights[i] 为高的最大矩形宽度并计算矩形的面积。假设数组 heights \textit{heights} heights 的长度是 n n n,该做法的时间复杂度是 O ( n 2 ) O(n^2) O(n2),会超出时间限制,因此需要优化。

创建两个长度为 n n n 的数组 left \textit{left} left right \textit{right} right,对于每个下标 i i i left [ i ] \textit{left}[i] left[i] right [ i ] \textit{right}[i] right[i] 分别记录对应的下标 j j j k k k。初始时, left \textit{left} left 的全部元素为 − 1 -1 1 right \textit{right} right 的全部元素为 n n n。这道题中可以假设 heights [ − 1 ] = heights [ n ] = − ∞ \textit{heights}[-1] = \textit{heights}[n] = -\infty heights[1]=heights[n]=。在遍历数组 heights \textit{heights} heights 的过程中填入 left \textit{left} left right \textit{right} right 的值。

为了得到 left \textit{left} left right \textit{right} right 的值,可以使用单调栈,单调栈存储数组 heights \textit{heights} heights 的下标,满足从栈底到栈顶的下标对应的元素单调递增。

从左到右遍历数组 heights \textit{heights} heights,当遍历到下标 i i i 时,进行如下操作:

  1. 如果栈不为空且栈顶下标对应的元素大于等于 heights [ i ] \textit{heights}[i] heights[i],则将栈顶下标出栈,并将栈顶下标对应的 right \textit{right} right 值设为 i i i,重复该操作直到栈为空或者栈顶下标对应的元素小于 heights [ i ] \textit{heights}[i] heights[i]

  2. 如果栈不为空,则栈顶下标对应的元素小于 heights [ i ] \textit{heights}[i] heights[i],因此将 i i i 对应的 left \textit{left} left 值设为栈顶下标;

  3. i i i 入栈。

上述操作中,下标 i i i 入栈的条件是栈为空或者栈顶下标对应的元素小于 heights [ i ] \textit{heights}[i] heights[i],因此可以保证从栈底到栈顶的下标对应的元素单调递增。

遍历结束之后,对于每个下标 i i i,令 j = left [ i ] j = \textit{left}[i] j=left[i] k = right [ i ] k = \textit{right}[i] k=right[i],则有 heights [ j ] < heights [ i ] \textit{heights}[j] < \textit{heights}[i] heights[j]<heights[i] heights [ k ] ≤ heights [ i ] \textit{heights}[k] \le \textit{heights}[i] heights[k]heights[i]。如果 heights [ k ] < heights [ i ] \textit{heights}[k] < \textit{heights}[i] heights[k]<heights[i],则高度为 heights [ i ] \textit{heights}[i] heights[i] 的最大矩形宽度即为 k − j − 1 k - j - 1 kj1。如果 heights [ k ] = heights [ i ] \textit{heights}[k] = \textit{heights}[i] heights[k]=heights[i],则高度为 heights [ i ] \textit{heights}[i] heights[i] 的最大矩形宽度大于 k − j − 1 k - j - 1 kj1,但是在遍历到下标 k k k 时可以得到高度为 heights [ k ] \textit{heights}[k] heights[k] 的矩形宽度为 right [ k ] − left [ k ] − 1 \textit{right}[k] - \textit{left}[k] - 1 right[k]left[k]1,该宽度大于 k − j − 1 k - j - 1 kj1。由于一定存在一个柱子,该柱子右边没有和该柱子高度相同的柱子,因此在遍历到该柱子时即可得到以该柱子的高度为矩形高度的矩形最大宽度,并计算得到矩形的最大面积。

对于下标 i i i,高度为 heights [ i ] \textit{heights}[i] heights[i] 的最大矩形的宽度为 right [ i ] − left [ i ] − 1 \textit{right}[i] - \textit{left}[i] - 1 right[i]left[i]1(根据上述分析,可能存在 k > i k > i k>i 满足 heights [ k ] = heights [ i ] \textit{heights}[k] = \textit{heights}[i] heights[k]=heights[i],但是这里仍然认为该宽度是最大矩形的宽度),面积为 ( right [ i ] − left [ i ] − 1 ) × heights [ i ] (\textit{right}[i] - \textit{left}[i] - 1) \times \textit{heights}[i] (right[i]left[i]1)×heights[i]。遍历 0 ≤ i < n 0 \le i < n 0i<n,即可得到柱状图中的矩形的最大面积。

考虑一个例子: heights = [ 2 , 1 , 4 , 4 ] \textit{heights} = [2, 1, 4, 4] heights=[2,1,4,4]。对应的 left = [ − 1 , − 1 , 1 , 1 ] \textit{left} = [-1, -1, 1, 1] left=[1,1,1,1] right = [ 1 , 4 , 3 , 4 ] \textit{right} = [1, 4, 3, 4] right=[1,4,3,4],矩形的最大面积是 8 8 8

对于下标 0 0 0,最大矩形的宽度为 1 − ( − 1 ) − 1 = 1 1 - (-1) - 1 = 1 1(1)1=1,高度为 2 2 2,面积为 2 2 2

对于下标 1 1 1,最大矩形的宽度为 4 − ( − 1 ) − 1 = 4 4 - (-1) - 1 = 4 4(1)1=4,高度为 1 1 1,面积为 4 4 4

对于下标 2 2 2,最大矩形的宽度为 3 − 1 − 1 = 1 3 - 1 - 1 = 1 311=1,高度为 4 4 4,面积为 4 4 4。注意 heights [ 2 ] = heights [ 3 ] \textit{heights}[2] = \textit{heights}[3] heights[2]=heights[3],因此最大矩形的宽度应该为 2 2 2,但是在下标 2 2 2 处计算的最大矩形的宽度为 1 1 1,在下标 3 3 3 处将会计算到最大矩形的宽度为 2 2 2

对于下标 3 3 3,最大矩形的宽度为 4 − 1 − 1 = 2 4 - 1 - 1 = 2 411=2,高度为 4 4 4,面积为 8 8 8

代码

class Solution {
    public int largestRectangleArea(int[] heights) {
        int length = heights.length;
        int[] left = new int[length];
        int[] right = new int[length];
        Arrays.fill(left, -1);
        Arrays.fill(right, length);
        Deque<Integer> stack = new ArrayDeque<Integer>();
        for (int i = 0; i < length; i++) {
            int height = heights[i];
            while (!stack.isEmpty() && heights[stack.peek()] >= height) {
                right[stack.pop()] = i;
            }
            if (!stack.isEmpty()) {
                left[i] = stack.peek();
            }
            stack.push(i);
        }
        int area = 0;
        for (int i = 0; i < length; i++) {
            area = Math.max(area, (right[i] - left[i] - 1) * heights[i]);
        }
        return area;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 heights \textit{heights} heights 的长度。需要遍历数组 heights \textit{heights} heights 一次,每个下标最多入栈和出栈各一次。

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 heights \textit{heights} heights 的长度。空间复杂度主要取决于两个数组 left \textit{left} left right \textit{right} right 以及栈空间,两个数组的长度都是 n n n,栈内元素个数不会超过 n n n

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值