【LeetCode】84. 柱状图中最大的矩形(单调栈)

1. 题目描述

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。

以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。

图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。

示例:

输入: [2,1,5,6,2,3]
输出: 10

2. 解题思路

其实可以把这个想象成锯木板,如果木板都是递增的那我很开心,如果突然遇到一块木板 i i i 矮了一截,那我就先找之前最戳出来的一块(其实就是第 i − 1 i-1 i1块),计算一下这个木板单独的面积,然后把它锯成次高的,这是因为我之后的计算都再也用不着这块木板本身的高度了。再然后如果发觉次高的仍然比现在这个 i i i 木板高,那我继续单独计算这个次高木板的面积(应该是第 i − 1 i-1 i1 i − 2 i-2 i2 块),再把它俩锯短。直到发觉不需要锯就比第 i i i 块矮了,那我继续开开心心往右找更高的。当然为了避免到了最后一直都是递增的,所以可以在最后加一块高度为 0 0 0 的木板。

这个算法的关键点是把那些戳出来的木板早点单独拎出来计算,然后就用不着这个值了。

参考:LeetCode讨论区.

2.1 暴力法

首先,要想找到第 i i i 位置最大面积(以第 i i i 根柱子为最矮柱子所能延伸的最大面积)是什么?

是以 i i i 为中心,向左找第一个小于 h e i g h t s [ i ] heights[i] heights[i] 的位置 l e f t i left_i lefti;向右找第一个小于于 h e i g h t s [ i ] heights[i] heights[i] 的位置 r i g h t i right_i righti,即最大面积为 h e i g h t s [ i ] ∗ ( r i g h t i − l e f t i − 1 ) heights[i] * (right_i - left_i -1) heights[i](rightilefti1),如下图所示:
所以,我们的问题就变成如何找 r i g h t i right_i righti l e f t i left_i lefti
最简单的思路就是,就是暴力法,直接分别在 i i i 左右移动。

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        n = len(heights)
        res = 0
        for i in range(n):
            l = i
            r = i
            while l >= 0 and heights[l] >= heights[i]:
                l = l - 1
            while r < n and heights[r] >= heights[i]:
                r = r + 1
            res = max(res, heights[i] * (r - l - 1))
        return res

但是,这是一个时间复杂度为 O ( n 2 ) O(n^2) O(n2),超时。
接下来想办法优化。

2.2 单调栈(空间换时间)

具体可以参考 LeetCode官方题解(视频动画).

所谓单调栈,本质上还是栈,即满足先进后出的原则,在Python 中实现可以用 List 实现。所谓单调,即单调递增或者单调递减。

  1. 单调递增栈可以找到左边第一个比当前出栈元素小(含等于)的元素
  2. 单调递减栈可以找到左边第一个比当前出栈元素大(含等于)的元素

具体解题步骤

  1. 本题中,当看到柱子递增时,将下标入栈,(栈中只存下标,因为高度可以直接通过下标访问);
  2. 当柱子下降时,将栈顶元素 stack[-1] pop 出来,并且计算当前的面积 h e i g h t [ i ] ∗ ( i − s t a c k [ − 1 ] − 1 ) height[i] * (i - stack[-1] - 1) height[i](istack[1]1),注意这里要用弹出一个栈顶后的新栈顶 stack[-1],具体可看下面代码

技巧:这里在最前面和最后面分别加了一个“哨兵”,目的是避免了特殊处理。

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        heights = [0] + heights + [0]
        n = len(heights)
        stack = []   # 只存下标,因为高度可以直接通过下标访问
        res = 0
        for i in range(n):
            while stack and heights[stack[-1]] > heights[i]:   # 判断栈顶元素是否比较大,即开始下降,需要出栈了
                top = stack.pop()    # 先出栈,栈顶元素就是前面低一点的那个
                res = max(res, heights[top] * (i - stack[-1] - 1)) # 这里不能用 i-top-1 因为top已经被弹出了
            stack.append(i)     # 如果柱子不断上升,则入栈
        return res

参考:

  1. LeetCode题解.
全部评论

相关推荐

bg:双非本,一段中小厂6个月测开实习今天发这个帖子主要是想聊一聊我秋招以来的一个发展我是在8月底辞职,打算秋招,可是看网上都说金九银十就想着自己就是一个普通本科生,现在九月份都是一些大神在争抢,所以9月份基本上没投,等到了10月份才开始秋招,可是这个时间好像已经有些晚了,今年秋招开启的格外早,提前到了7,8月份,我十月才开始,官网投了很多公司,没有任何一个面试机会,这个情况一直到了十月底才有了第一个面试,当时没有面试经验,所以不出意外的挂了后续就是漫长的投递,但是毫无例外没有面试,没有办法我只能另辟蹊径开始在BOSS上边投递,然后顺便也根据BOSS上边这个公司名称去浏览器搜索看看有没有官网投递渠道,毕竟官网上投递后还是可以第一时间被HR看到的,然后一直不停投递,一开始第一个星期基本上都是投的正式秋招岗位到了第二个星期才开始实习和正式一起投,到十一月底的时候已经沟通了700➕才有一共1个正式的,5个要提前实习的,3个实习的面试,最后结果是过了1个要提前实习的和2个实习的每次面试我都会复盘,发现这些小公司面试官问的五花八门,有的专问基础,有的专问项目,有的啥都问,不过自己也是看出来了一下门道,就是小公司不像大公司面试官那样能力比较强基本上你简历上边的他都会,然后会根据简历来问,小公司面试官他们更多的是看自己会什么,然后看看你简历上边哪些他也是会的然后来问,经过不断的复盘加上背各种各样面试题,到了11月底12月初才有了1个要提前实习的offer还有2个实习的offer,而且薪资待遇对我来说已经很可观了可是啊,人总是这样得了千钱想万钱,我又开始不满现状,但是此时的我面试能力经过这么多面试和复盘已经很强了,然后在十二月份运气爆棚,被极兔和小鹏补录捞起来面试,还有个百度测开的实习面试,这个时候因为有了offer所以感觉有了底气,面试也很自信,最后结果是全部都过了那个时候我感觉自己真的很厉害,我问了极兔那边的HR像我这样的双非本收到offer的在极兔有多少?他告诉我产研岗90%都是硕士,10%里边基本上都是211,985,想我这样的很少很少,那一刻感觉自己超级牛逼,小鹏就更不用说了,最后也是不出意外选择了小鹏所以我就我个人经历想对和我学历履历差不多的牛友一些建议第一:秋招一定要趁早,真到了9,10月,那个时候可能你投的结果可能还不如7,8,11月,第二:最好先拿小公司实习或者正式练练手,提升一下面试能力,我个人觉得因为小公司问的五花八门所以你会更加横向去提升自己能力,而且大公司其实面试没有那么难,除了一些非常卷的岗位,公司大神比较多会问的很难,一般好点的公司都不会问的那么难,他们也知道都是应届生不会要求那么高第三:当有一定能力后,就是坚持了,对于我们这样的学历,没有特别强的履历情况下,就是要抓住提前批和补录的机会,这个时候各方面不会卡的很严,是我们很好很好的一个机会第四:就是运气也是很重要的一部分,不过这个很难去说什么最后祝各位牛友都能收获自己满意的offer😁😁😁
秋招,不懂就问
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务