代码随想录算法训练营Day60 | 84. 柱状图中最大的矩形

84. 柱状图中最大的矩形

题目链接 | 解题思路

本题和接雨水的题目相互呼应,但是难度略有提升,同样是一道非常棒的题!

在接雨水中,需要找到每一列的左侧最大值和右侧最大值;在本题中,需要找到每一列的左侧第一个更小值和右侧第一个更小值。这个要求的变化加大了双指针的难度,所以优先讨论更加适用的单调栈解法。

和上一题一样按行来计算矩形面积,对于固定的一列,需要这一列的高度、对应的左边界、对应的右边界。但这一题,单调栈的储存顺序发生了变化。

在这里插入图片描述

  1. 单调栈内的元素顺序、如何取值
    • 栈内元素应该是 top-bottom 递减的,此时向右搜索,找到当前元素大于栈口元素的时候就代表着找到了栈口元素这一列代表的高度的右边界
    • 当前元素大于栈口元素时:
      • 右边界即为当前元素
      • 基准位置即为栈口元素
      • 左边界即是栈口元素的内部相邻元素
  2. 当前元素与栈口元素相等:和接雨水的处理一样
    • 之所以能够对元素值相等的情况有多种处理,是因为在计算中只需要他们代表的值来作为基准高度,而不需要他们的确切下标,利用的下标是左边界和右边界。

对于当前元素与栈口元素的三种比较情况,也和接雨水中的方法无异。

首尾加 0

本题最大的不同就在于需要在输入数组的首尾各加入一个 0,从而解决一些特殊的输入数组:

  • 单调递增数组:heights = [2, 4, 6, 8]

    • 对这样的输入,每一步循环都会将当前下标压入栈。直到遍历结束,也不会有执行计算面积的操作,因为没有碰到一个更小的元素。所以在尾部加入一个 0,可以确保在这种情况下执行正确的面积计算。
  • 单调递减数组:heights = [8, 6, 4, 2]

    • 对这样的输入,每一步循环都会触发面积计算。然而,如上所述,面积的计算需要当前基准高度、左边界、右边界。对第二个下标 1 执行面积计算时,得到右边界 1,基准下标 0。弹出 0 后已经得到了一个空栈,无法得到左边界。所以,这种情况下也不会执行计算面积,因为总是不能得到左边界。
    • 所以在首部加入一个 0,可以确保在这种情况下执行正确的面积计算。

可以看到,在接雨水中我们没有特殊考虑过这两种情况。因为在接雨水中,如果不能够形成一个完整的谷,那么就不需要进行任何计算,也就是说单调递增、递减的数组本来就不该有任何计算结果。而在本题,任何输入都应该正确计算面积,所以不能只计算完整的峰,而要全面考虑。

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        heights.insert(0, 0)
        heights.append(0)

        result = 0
        stack = [0]
        for i in range(1, len(heights)):
            if heights[i] > heights[stack[-1]]:
                stack.append(i)
            elif heights[i] == heights[stack[-1]]:
                stack.pop()
                stack.append(i)
            else:
                while (len(stack) > 0 and heights[i] < heights[stack[-1]]):
                    mid = stack[-1]
                    stack.pop()
                    if len(stack) > 0:
                        left = stack[-1]
                        curr_width = i - left - 1
                        curr_height = heights[mid]
                        result = max(result, curr_height * curr_width)
                stack.append(i)
        return result

双指针

和接雨水一样,本题同样有双指针 + dp 的解法,但是这个解法的时间复杂度有些可疑。
和接雨水相比,本题中的双指针 + dp 会更加复杂一些,因为要求的不再是最大值而是第一个更小值的下标,dp 的递推变得很困难,我也不确定具体的复杂度。

这题的 dp 部分有点像是 KMP,感觉很有意思。

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        # records the index of the first smaller value on the left of i
        left_smaller_idx = [0] * len(heights) 
        left_smaller_idx[0] = -1            # no smaller value on the left, representing non-existence
        for i in range(1, len(heights)):
            temp = i - 1
            while (temp >= 0 and heights[temp] >= heights[i]):
                temp = left_smaller_idx[temp]
            left_smaller_idx[i] = temp
        
        # records the index of the first smaller value on the right of i
        right_smaller_idx = [0] * len(heights) 
        right_smaller_idx[-1] = len(heights)            # no smaller value on the right, representing non-existence
        for i in range(len(heights) - 2, -1, -1):
            temp = i + 1
            while (temp < len(heights) and heights[temp] >= heights[i]):
                temp = right_smaller_idx[temp]
            right_smaller_idx[i] = temp

        result = 0
        for i in range(len(heights)):
            result = max(result, heights[i] * (right_smaller_idx[i] - left_smaller_idx[i] - 1))
        
        return result
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值