leetcde84:柱状图中最大的矩形


在这里插入图片描述
在这里插入图片描述

思路一:暴力法

对于每一个高度i,分别求他左边和右边第一个比他小的位置,则该高度下的最大面积为heights[i] * (r - l - 1)

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

复杂度为O(n^2),在力扣上超时,所以我们可以对该算法进行优化

思路二:优化的思路一

可以在思路一的基础上,借鉴42题雨水中两次遍历的方法,的思路,对于所有元素,建立左边第一个比对应元素小的元素位置矩阵left,和右边第一个比他小的位置矩阵right,如left[2]heights[3]左边第一个比heights[3]小的元素,right[3]同理。
那么对于思路一的优化在于,举例子来说:对于i位置上求heights[i]第一个比他小的位置,如果heights[i-1] >= heights[i],那么位置i要求的东西,和i-1位置要求的的左边第一个比heights[i-1]小的元素位置,两者是相同的,即求i位置得相关解就可以转化为为i-1位置的相关解。利用这一点,可以起到剪枝的作用,right同理。

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        if not heights:
            return 0
        n = len(heights)
        res = 0
        left = [0] * n
        right =[0] * n
        left[0] = -1  #令首元素为-1(由推导得知)
        right[-1] = n  #令末尾元素为n
        for i in range(1,n):
            tmp = i-1   #令tmp初始化为i-1位置
            while tmp >= 0 and heights[tmp] >= heights[i]:   
                #i-1位置的高度大于i位置的高度,那么求i位置得相关解就可以幻化为i-1位置的相关解
                tmp = left[tmp]
            left[i] = tmp
        for i in range(n-2,-1,-1): 
            tmp = i+1
            while tmp < n and heights[tmp] >= heights[i]:
                tmp = right[tmp]
            right[i] = tmp
        for i in range(n):
            res = max(res,heights[i] * (right[i]-left[i]-1))
        return res

思路三:使用单调栈

简洁版思路:

由思路一的寻找左右第一个比当前柱子小的柱子,可以借助单调递增栈,因为对于栈中的柱体来说,栈中下一个柱体就是左边第一个高度小于自身的柱体。
因此做法就很简单了,我们遍历每个柱体,若当前的柱体高度大于等于栈顶柱体的高度,就直接将当前柱体入栈,否则若当前的柱体高度小于栈顶柱体的高度,说明栈顶柱体找到了右边的第一个小于自身的柱体,那么就可以将栈顶柱体出栈来计算以其为高的矩形的面积了。注意前后加两个高度为0的柱子,方便计算。用java实现
(这里使用LinkedList实现栈结构,栈操作对应LinkedList方法:
push——addFirst
pop——removeFirst
peek——getFirst
isEmpty——isEmpty
嫌麻烦的话直接用Stack类:Stack<> stack = new Stack<>())

class Solution {
    public int largestRectangleArea(int[] heights) {
        LinkedList<Integer> stack = new LinkedList<>();
        int[] tmp = new int[heights.length + 2];
        System.arraycopy(heights,0,tmp,1,heights.length);
        
        int res = 0;
        for (int i = 0; i < tmp.length; i++){
            while (!stack.isEmpty() && tmp[stack.getFirst()] > tmp[i]){
                int h = stack.removeFirst(); 
                res = Math.max(res, tmp[h]*(i - stack.getFirst() - 1));
            }
            stack.addFirst(i);
        }
        return res;        
    }
}

以下为啰嗦 详细版求解过程

1 如何求最大矩形面积

现在我们从头开始讲,如果要求只能遍历一次,那么如何求最大面积?
我想到一个思路,那就是先把能完全包含各个柱状图的矩形的最大面积求出来,然后求出其中最大值即可。以例题来说就是

能完全覆盖第0个柱子的最大矩形

在这里插入图片描述
能完全覆盖第1个柱子的最大矩形
在这里插入图片描述
能完全覆盖第2个柱子的最大矩形
在这里插入图片描述
能完全覆盖第3个柱子的最大矩形
在这里插入图片描述
能完全覆盖第4个柱子的最大矩形
在这里插入图片描述
能完全覆盖第5个柱子的最大矩形
在这里插入图片描述
如此这般,就能够覆盖所有分支而又不遗漏,将这6个矩形的面积比较下就知道最大面积了。

2 如何求以某个柱子为高的最大矩形

在这里插入图片描述
我们就以例题中第4个,高为2的柱子举例好了。
矩形的面积=高*宽。
我们很高兴的发现,在这个分支情况下,我们已经知道高为2了,那么宽度如何求呢?
通过观察,我们发现矩形的左边沿是左边第一个高比2小的柱子,右边沿是右边第一个高比2小的柱子(将高为3的柱子的右面看作还有一个高为0的柱子)
如此它的宽度是6-(1+1)=4

这时可能你已经一头雾水了,6是啥?1是啥?为什么要加1?
如果你这么问,我打赌你肯定是如下图这么想的。
在这里插入图片描述
如果你将柱子的左下角对应序号的话,或许会好一些。
在这里插入图片描述
我们说了矩形的左边沿是第1个柱子的右边,那可不就是(1+1)了吗?
矩形右边沿是第6个柱子的左边,直接就是6.
宽度自然就是6-(1+1)=4了。

注意:基于各个高度的最大矩形是在出栈的时候计算的,因此必须要让所有高度都出栈。这里是利用单调栈的性质让其全部出栈,即在原始数组后添一个0.

3 如何寻找左右边沿

我们已经说了,左边沿是左边第一小与本柱子高的柱子的右边,右边沿也是同理。
这正好可以用单调栈。
当第i个柱子进栈时,如果栈顶柱子(此处记作柱子A)的高度低于或等于第i个柱子,则第i个柱子进栈;
如果高于第i个柱子,则出栈,并计算以柱子A为高的矩形最大面积。

高度:就是柱子A的高度
右边沿:正好是i(由于单调栈的性质,第i个柱子就是右边第一个矮于A的柱子)
左边沿:单调栈中紧邻A的柱子。(如果A已经出栈,那么左边沿就是A出栈后的栈顶)而且是该柱子的右边,所以要+1.
因此,完全覆盖第index个柱子的最大矩形的面积如下(stk是单调栈)

以上部分参考:https://blog.csdn.net/Zolewit/article/details/88863970

4 代码实现

class Solution:
    def largestRectangleArea(heights):
        res = 0
        stack = [-1]
        heights = heights + [0]
        for i in range(len(heights)):
            while stack and heights[i] < heights[stack[-1]]:
                top = stack.pop()
                res = max(res,heights[top] * (i-stack[-1]-1))
            stack.append(i)
        return res

5 代码说明

将stack的首元素置为-1和在heights中添加高度0的原因是:
在进行栈弹出操作时,只有heights[i] < heights[stack[-1]]才能弹出,假设栈中只剩下-1,此时heights[stack[-1]] = heights[-1] = 0,也就是我们在heights中添加的最后的高度0。高度0已经是最小的高度了,没有别的高度能比他再小了,那么就不满足弹出条件,while语句结束,开始进行下一次大循环。所以stack中的-1元素会一直被保留,直到循环结束。
那么为什么stack中的-1会保留到循环结束呢?我们注意到,我们的循环次数是加了0之后的heights的长度,即原来的高度矩阵的长度+1。那么在进行到高度为0的最后一个元素时会发生如下情况:
由于当前的heights[i]为0,如前所述,0是最小的高度,一直满足弹出条件,此时会将stack中除了首元素-1之外的所有元素都弹出作为top,以进行面积的计算。而首元素-1则不会被弹出,因为heights[-1]就是此时的heights[i],都等于0,而弹出的判断语句中是没有等号的,所以-1不会弹出,那么while语句结束,此时大循环也结束。
综上,stackheights中添加的元素对结果没有影响,起到了帮助计算的作用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值