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


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

    柱状图中最大的矩形

    在这里插入图片描述

    思路

    这个题最明显的思路就是:矩形面积=底×高。

    • 版本1:底的长度可以通过二重循环来完成,高通过循环来寻找最小值。
    • 版本2:底的长度可以通过二重循环来完成,高通过线性比较当前遍历到的高度和之前记录的max(minh, h)

    可以发现,通过遍历底×高的方法来做,优化的上限就到此为止。那么我们想一想,能否通过遍历高×底的思想呢?

    如果能通过遍历每个高,这只需要O(n)的时间复杂度,同时找到以这个高度为高的最大矩形的底有多长就好了!

    考虑对某一个高而言,它的底究竟是什么?如下图(参考链接)所示:
    在这里插入图片描述

    也就是我们如果能在遍历到下标为2的柱子的时候,得到这两个信息就好了:

    • 左侧比他矮的最近的柱子的下标,也就是1
    • 右侧比他矮的最近的柱子的下标,也就是4

    我们想到一种结构:单调栈可以存储单调递增/递减的柱子下标。这样上面提到的第一个信息,也就是遍历时左侧比他矮的最近的柱子下标就知道了。

    那么右边的呢?

    右边的相当于现在的高度而言其实是“未来”的,也就是还没遍历到的。那我们考虑在“未来”的遍历过程中,无非是出现“比现在这个高度高” 和 “比现在这个高度低” 两种情况,边界的等于我们稍后再讨论。

    当遇到比现在高度高时,就需要添加到栈中。因为栈必须维护 “前一个存储的下标是后一个存储下标的左边界”

    当遇到比现在高度低的时候,其实就是上面提到的第二个需要知道的信息,右侧比他矮的第一个柱子的下标!

    下面我们来整理一下思路:

    • 遍历每个高度,记作height
    • 单调栈维护左侧比height矮的最近的柱子的下标。称为lo。
    • 当右侧出现第一个比height矮的柱子时,计算height×底,此时底的长度是(hi-lo-1)。

    而在代码实现的过程中,因为栈具有记录功能,所以上面我们所说的遍历每个高度,计算面积的过程其实不是在当前高度被遍历到的时候计算的,因为此时hi的值不知道。

    我们使用栈记录了额外的信息,也就是待计算的高度height。把真正计算面积,更新最大面积的过程延迟到了hi出现的时候!我认为这才是栈作为数据结构之所以有用的原因。

    而单调栈则相比栈记录了更多的信息,也就是比height矮的最近的柱子的下标lo就是height的前一个位置。

    代码实现

    class Solution:
        def largestRectangleArea(self, heights: List[int]) -> int:
            ans, stack = 0, []
    
            # 添加哨兵
            heights = [0]+heights
            heights.append(0)
    
            # 左侧限位lo, 右侧限位hi
            lo, hi = 0, 0
            for hi, tmp in enumerate(heights):
            	# 注意边界条件,栈为空时直接入栈
                while (stack and tmp<heights[stack[-1]]):
                    height = heights[stack.pop()]
                    lo = stack[-1]
                    ans = max(ans, height*(hi-lo-1))
                stack.append(hi)
            return(ans)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    复杂度分析

    时间复杂度

    在遍历数组时,直方图的每根柱子都入栈,出栈一次,并且在每个柱子的下标出栈时计算以它为顶的最大矩形面积,这些操作对每根柱子而言复杂度都是O(1),故总的时间复杂度为O(n)

    空间复杂度

    需要一个辅助栈,栈中可能有O(n)根柱子在数组中的下标,故空间复杂度为O(n)

    矩阵中最大的矩形

    有了上面的铺垫,将一维的情况扩展至二维即可。如题:
    在这里插入图片描述

    如果把每一行/每一列看作一个heights数组,那么问题就可解了。

    考虑每一行/每一列的heights应该是多少?

    这里以行为例,对于第一行而言,heights应该是【10100】,第二行,则是【20211】,我们可以通过动态规划的方法更新该数组,规律为:

    • 如果位置为1,则height+1
    • 否则height变成0

    为什么是正确的?因为最大的矩阵的高度永远被限制在最小的height中,所以即使我们看似加入了y轴以下的部分(有的height出现的位置早,有的出现的位置晚),也会被最小的height限制住。

    代码非常简单,只是在每次调用计算柱状图中最大的矩形的外面,加了新的循环,用于更新heights数组。

    class Solution:
        def maximalRectangle(self, matrix: List[str]) -> int:
            if not matrix:
                return 0
            col_len = len(matrix[0])
            
            # 记录当前行每一个“柱子”的高度,0和最后一位是哨兵
            heights = [0 for i in range(col_len+2)]
            ans, stack = 0, []
            for line in matrix:
                for i in range(col_len):
                    # 如果是1,则长度为上一行长度+1,否则为0
                    heights[i+1] = heights[i+1]+1 if line[i]!='0' else 0
    
                # 栈清空
                stack.clear()
                # 左侧限位lo, 右侧限位hi
                lo, hi = 0, 0
                for hi, tmp in enumerate(heights):
                    # 注意边界条件,栈为空时直接入栈
                    while (stack and tmp<heights[stack[-1]]):
                        height = heights[stack.pop()]
                        lo = stack[-1]
                        ans = max(ans, height*(hi-lo-1))
                    stack.append(hi)
            return(ans)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
  • 相关阅读:
    Mathorcup数学建模竞赛第四届-【妈妈杯】B题:基于协同过滤的书籍推荐模型
    操作系统4小时速成:操作系统发展和分类,运行环境:运行机制和内核,用户态非特权,核心态特权,中断技术,访管指令
    Spring Cloud教程 第十一弹 Spring Cloud Config连接git和数据库
    异常语法详解
    termius mac版无需登录注册直接永久使用
    java计算机毕业设计springboot+vue南天在线求助系统
    【微机原理笔记】第 1 章 - 微型计算机基础概论
    一.node的事件处理;二.node的全局对象;三.node的readline模块;四.node的Web编程
    锐捷校园网自助服务系统 login_judge.jsf 任意文件读取漏洞复现(XVE-2024-2116)
    “12306”的架构到底有多牛逼?
  • 原文地址:https://blog.csdn.net/Can__er/article/details/127869968