今天是LeetCode专题第52篇文章,我们一起来看LeetCode第84题,Largest Rectangle in Histogram(最大矩形面积)。

这道题的官方难度是Hard,点赞3581,反对只有80,通过率在34.7%左右。从通过率上来看,难度其实还可以,并没有特别大,但是这道题的点赞比很高,说明题目的质量很好。实际上也的确如此,这题非常经典,我个人也非常推荐。建议大家有能力的都做一下本题,一定会很有收获。

题意

假设我们有一系列宽度相同都为1的矩形竖直地摆放在一起,请问摆放而成的这个图案所能围成的最大矩形的面积是多少?

比如上图当中,我们有6个矩形,它们的宽度都是1。我们能找到的最大矩形应该是中间5和6围成的矩形:

题目给定一个含有若干个整数的数字,表示这些矩形的高度,要求返回能找到的面积最大的矩形的面积。

样例

Input: [2,1,5,6,2,3]Output: 10

区间求最值

拿到手应该能感受到这题的难度,我们一上来的确没有什么太好的思路,题目也比较明确,没有太多可以分析的入手点。所以我们可以先来思考一下最简单的解法。

最简单的解法就是找出能够围成的所有矩形,然后比较它们之间的面积,得出其中的最大面积。我们很容易可以想到可以遍历矩形的起始位置,这样就得到了矩形的宽。至于矩形的长也很简单,就是选定的这个区间段里的最低高度

我们可以做一个小小的思路转换,假设这些矩形都是木条,我们是要选出木条来制作木桶。那么根据木桶效应,木桶围成的水的高度取决于最短的那根木条,同样围成矩形的面积的高取决于这些矩形当中最矮的那个。也就是说,当我们确定了区间之后,我们只需要找到区间里最小的数就可以了。所以这题就转化成了区间求最值的问题,比如上图当中,如果我们选择最后三个矩形,那么它的高度就是2。

我们假设一共有n个长条矩形可供选择,那么我们可以选出的首尾组合就是C_n^2,大概是n的平方量级个区间。对于每个区间,我们需要遍历它们中的元素获取最小值,这需要O(n)的遍历时间,所以整体的复杂度应该在O(n^3)量级。显然这是一个非常大的数量级,当n超过1000就很难计算出解了。

这个思路显然不够好,我们想要对它进行优化也不容易。比如说如果你学过线段树这类的数据结构,可能还会想到使用线段树,我们可以将每次求最小值的查询优化到O(log n),但即便如此最终的复杂度也很高。这是因为我们遍历区间首尾位置就耗费了O(n^2),而这是很难优化的。所以这个思路的极限已经确定了,我们无法做出大的优化

从这点出发,如果存在更好的解法,那么一定不是通过这种方式进行的。

逆向思维

上面的一种思路虽然不太可行,但是它提供了一种正向思路。我们搜索所有的区间,然后通过区间里的木条确定区围成矩形的高度,就得到了矩形的面积。

既然这条路走不通,我们能不能反向思考呢?我们假设我们找到了答案,它是区间[a, b]段的木条围成的矩形,它的高度是h。那么根据木桶效应,a到b区间段的木条当中一定有一根的长度是h。比如下图当中[5, 6, 2, 3]如果要围成矩形,那么高度只能是2。

既然如此,我们可以寻找以某根木条为短板所能构成的最大矩形。比如上图当中,如果我们以第一根木条去寻找,就只能找到它本身,所以这个矩形的面积就是1 x 2 = 2。如果以第二根木条为短板去寻找,可以找到整个区间,它对应的面积就是1 x 6 = 6。

因为我们只有n个木条,以每个木条为短板寻找最大矩形,那么我们一定可以找出最多n个矩形。最终的答案一定在这n个矩形当中,在正向思维当中我们寻找木条区间需要O(n^2)的复杂度,然而我们寻找短板,只需要O(n),也就是说这种思路的搜索空间更小,只要我们保证搜索的效率,就可以更快地找到答案。

为了找到每个木条对应的最大矩形,我们需要找到每个短板向左以及向右能够延伸到的最远位置。比如上图例子当中,根据每个木条向右延伸的最远位置,我们可以得到[0, 5, 3, 3, 5, 5],同样,我们可以得到每根木条向左延伸的数组:[0, 0, 2, 3, 2, 5]。有了这两个数组之后,我们就可以计算出以每一根木条为短板的最大矩形的面积,在这其中面积最大的那个就是答案。

这个位置我们可以使用单调栈来求,我们用一个有序的栈来维护延伸的位置。举个例子,我们用从栈底往栈顶递增的单调栈来维护每根木条向右延伸的位置。当我们遇到一根新的木条时,会弹出栈中所有比它长的值。对于这些值来说,这根新的木条就是它的右边界。比如[5, 6, 2],一开始读到5,入栈。接着读到6,由于6大于栈顶的5,所以6入栈。最后读到2,由于2比6小,所以6出栈,对于6来说,2的位置就是它的右侧边界。正是由于2比它小,所以它才需要出栈,也说明了2的左侧的元素都比6来的大,否则6在之前就应该出栈了。同理,2也是5的右侧边界。

如果你不了解单调栈,可以参考一下之前的文章:

单调栈、构造法、two pointers,这道Hard题的解法这么多?

我们把以上的逻辑翻转,就得到了左侧边界求解的逻辑。左右边界有了之后,我们只需要乘上它们之间的区间长度就得到了矩形的面积。

接着,我们来写出代码:

class Solution:    def largestRectangleArea(self, heights: List[int]) -> int:        n = len(heights)  # 左侧边界初始化为0        left_side = [0 for i in range(n)]        # 右侧边界初始化为n-1        right_side = [n-1 for _ in range(n)]                stack_left = []        stack_right = []                for i in range(n):            h = heights[i]            # 弹出栈中所有比当前元素小的值            # 注意,栈内存储的是下标            while len(stack_right) > 0 and h < heights[stack_right[-1]]:                tail = stack_right[-1]                stack_right.pop()                right_side[tail] = i - 1                        # 当前元素入栈            stack_right.append(i)                        # 把坐标翻转,等价于逆向遍历            i_ = n - 1 - i            h = heights[i_]                        # 维护单调栈的逻辑同上            while len(stack_left) > 0 and h < heights[stack_left[-1]]:                tail = stack_left[-1]                stack_left.pop()                left_side[tail] = i_ + 1                            # 当前元素入栈            stack_left.append(i_)                        ret = 0        for i in range(n):            # 矩形面积等于右侧边界-左侧边界+1 x 高度            cur = (right_side[i] - left_side[i] + 1) * heights[i]            ret = max(ret, cur)        return ret

总结

想要把这道题做出来,单单理清楚题意和单单会单调栈都是没有用的。既需要理清楚题意,从最简单的解法出发推导出优化的方法,也需要深刻理解单调栈这个数据结构,才可以灵活应用。

另外,在代码当中需要特别注意边界的情况。比如初始化时左右边界的设定,以及可能会出现连续相等元素的情况,这些都需要纳入考虑。代码虽然看起来简单,但是隐藏了很多细节,所以只看代码是没用的,最好还是能亲自实现一下。

今天的文章到这里就结束了,如果喜欢本文的话,请来一波素质三连,给我一点支持吧(关注、转发、点赞)。

本文始发于公众号:TechFlow

的input最大长度_LeetCode 84 | 单调栈解决最大矩形问题相关推荐

  1. 2022-11-16 每日打卡:单调栈解决最大矩形问题(一维直方图,二维最大红矩形)

    每日打卡:单调栈解决最大矩形问题(一维直方图,二维最大红矩形) 柱状图中最大的矩形 思路 这个题最明显的思路就是:矩形面积=底×高. 版本1:底的长度可以通过二重循环来完成,高通过循环来寻找最小值. ...

  2. 单调栈解决取矩形问题

    单调栈解决取矩形问题 ​ 前言:这一类问题不知道是什么问题,emmm大概意思就是从一个混合着可行点和不可行点的矩形中能取出的充满可行点的矩形的数目.这一类问题应该有一个官方的名字,大家如果知道可以在评 ...

  3. 将一个数组中的值按逆序重新排放。_六十五、下一个更大的数系列,单调栈解决方法...

    「@Author:Runsen」 ❝ 编程的本质来源于算法,而算法的本质来源于数学,编程只不过将数学题进行代码化. 「---- Runsen」 ❞ 据说,放张小姐姐觉得照片可以提高阅读量,图是来源学校 ...

  4. 洛谷P4147 玉蟾宫(单调栈解决)

    题目 题目链接 题目背景 有一天,小猫 rainbow 和 freda 来到了湘西张家界的天门山玉蟾宫,玉蟾宫宫主蓝兔盛情地款待了它们,并赐予它们一片土地. 题目描述 这片土地被分成 N\times ...

  5. 【栈】python、单调栈解决收集雨水问题、力扣42题

    以下是leetcode 42原题: 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水. 示例 1: 输入:height = [0,1,0,2,1,0,1 ...

  6. 单调栈解决维持相对位置不变最小/最大字典序问题

    多次碰到这类维持相对位置不变,删除某些元素维持最小or最大字典序问题,这里记录一下: 首先给出一个经典的例子: 我们想要维持最小或者最大,无非是要保持相对有序的情况下,保持一个递增或者递减栈,其实就是 ...

  7. 六十五、下一个更大的数系列,单调栈解决方法

    @Author:Runsen 编程的本质来源于算法,而算法的本质来源于数学,编程只不过将数学题进行代码化. ---- Runsen 据说,放张小姐姐觉得照片可以提高阅读量,图是来源学校的2020新生. ...

  8. 使用单调栈解决接雨水问题——LeetCode 42 接雨水+单调栈说明

    题目内容 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水. 示例 1: 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] 输 ...

  9. fzoj Problem 2190 非提的救赎 【单调栈】

    题目链接:fzoj Problem 2190 非提的救赎 Problem 2190 非提的救赎 Accept: 106 Submit: 312 Time Limit: 1000 mSec Memory ...

最新文章

  1. AI安防落地靠什么?大规模生产是关键
  2. 如何创建高质量的TypeScript声明文件(六) - 示例
  3. hive性能调优实战pdf_1分钟带你入门JVM性能调优,实战解析调优工具
  4. BugKuCTF WEB 头等舱
  5. recyclerView + GridLayoutManager 实现任意网格布局+拖拽排序
  6. HTTP的请求报文与响应报文
  7. (54)FPGA条件选择有优先级(if-else)
  8. 计算机编程语言的代码——编码
  9. html网页转换swf格式,swf格式转换器 轻松将swf转mp4(swf转avi)视频格式
  10. Delicious Retouch 5—PS磨皮插件
  11. html 字体居中 font,CSS字体(font)
  12. QQ坦白说BUG 找出对方
  13. 数值重映射方法(Remap)
  14. PhotoSweeper X for Mac(重复照片清理工具)
  15. 软件公司绩效考核(大家提提建议)
  16. 晶联讯12864液晶+STM32+HAL库 IO模拟SPI成功实现显示。
  17. 【广告系列一】广告相关名词 CTR/CVR/eCPM...
  18. Java数据结构之中缀表达式转后缀表达式
  19. chrome浏览器页面自动翻译页面失效问题解决
  20. 如何用adb 安装安卓测试包for mac

热门文章

  1. php 什么是函数式编程,函数式编程的介绍和归纳总结(附代码)
  2. mysql 8 启动失败(本地计算机上的mysql服务启动后停止。某些服务再未由其他服务或程序使用时将自动停止)
  3. 理解Dubbo的调用流程与Dubbo多协议解析
  4. 订阅号如何配置服务器信息,订阅号服务号区别和订阅号启动服务器配置
  5. mysql语句命令_MySQL语句和命令大全
  6. apache shiro怎么升级_Spring Boot 整合 Shiro ,两种方式全总结!
  7. Stream将List转换为Map
  8. 小学计算机知识点,小学信息技术单元知识点目录介绍
  9. 汇编语言定时器转化为c语言,不用定时器和汇编语言,只用C语言实现精确无误的延时...
  10. 引用类型 —— Array类型