柱状图中最大的矩形

496 阅读3分钟
原文链接: zhuanlan.zhihu.com

题目描述

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。



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



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

题目来源

Loading...leetcode-cn.com图标

解题思路:使用栈的数据结构解答该题目

调试环境:python3 + MacOS

难点:python里面栈的数据结构可以很方便使用list的pop和append方法来实现压栈和出栈,也就是先进后出的数据结构。此题目的难点在于把索引压栈之后如何去求解局部最大长方形面积。

推导思路(非严格数学证明):

(1)长方形的面积为长✖️高,此题目中长为索引值差值,比如求n个相邻柱子长方形长为n索引值 减去第一个柱子索引值,而高是最低柱子值决定的。所以局部面积最大 area=(index_n-index_m)* lowest_highest。把索引值存入一个stack的数据结构中,这样可以方便求宽度。

(2) 假设数组为height=[2,3,4]先考虑升序情况求局部最大值,通过画图推导很容易得出*升序情况下局部最大值公式(stack[-1]-index) * height[index]

我们可以得到升序求最大面积代码:

def _increase_list(self, heights: list) -> int:
        # [1, 3, 4, 7]
        stack_index = [index for index in range(len(heights))]
        max_area = 0
        top_index = len(stack_index)
        while stack_index:
            index = stack_index.pop()
            width = top_index - index
            area = heights[index] * width
            print(area)
            max_area = max(max_area, area)
        return max_area

(3) 最关键一步来了,如果将出现降序怎么办呢?根据第一步我们知道局部最大是由最低的柱子决定的,所以我们不必急着算出局部升序所有面积值,利用栈的性质,弹出末尾最大值,然后比较是否任然升序,这样保证我们始终构造了一个升序的模型,比如柱子高度为[1,2,4,3]时候,图画的有点丑~

说明:当3进入之后我们发现比最高的4更小,这个时候我们弹出4,入栈3,我们发现由于索引值是增长的,所以并不影响高为2的时候求最大面积,公式和之前方式基本一致,这个是我觉得这个算法最tricky的地方,如果第一次接触此类题目,很难想到。也是因为这一步,避免了暴力方法频繁计算面积,大大降低计算时间。

理清楚思路之后,我们再整理代码。

class Solution2:
    def largestRectangleArea(self, heights: list) -> int:
        """
        :type heights: List[int]
        :rtype: int
        """
        heights.append(-1)  # 末尾添加一个-1,方便我们用索引值相减,求出长方形的宽。假设最后一个柱子为最大的单个柱子,索引值为index,则宽度为len(height)-index
        max_area = 0
        stack_index = []
        # 索引值入栈
        index = 0
        while index < len(heights):
            if stack_index:  # 如果不为空,判断是否升序,如果升序,则入栈
                if heights[index] >= heights[stack_index[-1]]:
                    stack_index.append(index)
                    index = index + 1
                else:  # 否则开始计算,直到升序
                    previous_index = stack_index.pop()
                    if stack_index:  # 如果索引栈不为空,则用当前索引减去栈定索引值得到宽
                        max_area = max(max_area, (index - stack_index[-1] - 1) * heights[previous_index])
                        # 注意:如果没有高度为0的柱子求宽度的表达式index - previous_index成立,如果有则有问题
                        # max_area = max(max_area, (index - previous_index) * heights[previous_index])
                    else:  # 如果索引栈已经为空,则用当前索引值最为宽,比如传入值为[3,2,1]这种情况,因为已经为空,说明之前的柱子都更高
                        # 如果有0高度的柱子,必定在最前面,此时高度为零,不影响计算
                        max_area = max(max_area, index * heights[previous_index])
            else:  # 如果为空,则入栈
                stack_index.append(index)
                index = index + 1
        return max_area

需要注意一点是再求宽度时候刚开始使用表达式index - previous_index,如果没有高度为零的柱子是没有问题的,如果出现高度为零的柱子,则有问题。

代码地址:

Danielyan86/LeetcodePython3github.com图标