蠡口84. Largest Rectangle in Histogram

题意:给定一个数组代表一个柱状图里每根柱子的高度,每根柱子宽为1,找到柱状图中面积最大的矩形的面积。

看到这道题首先想到的是DP,用两维数组记录i~j之间的矩形面积,再遍历搜索一遍即可求得最大面积,时间复杂度为O(n2). 这样是会超时的,代码如下:

class Solution(object):
    def largestRectangleArea(self, heights):
        """
        :type heights: List[int]
        :rtype: int
        """
        n=len(heights)
        if n==0: return(0)
        #1) 初始化dp矩阵: dp[i][j]表示heights[i:(j+1)]之间最矮的柱子高度
        dp=[[0 for j in range(n)] for i in range(n)]
        #2) 处理边界情况
        for i in range(n):
            dp[i][i]=heights[i]
            if i<n-1: dp[i][i+1]=min(heights[i],heights[i+1])
        #3) 建立递推关系:注意是从diag向右斜上方递推
        #dp[i][j]的值应该是dp[i+1][j-1],heights[i]和heights[j]三者的最小值
        for j in range(2,n):
            for i in range(n-j):
                dp[i][i+j]=min(dp[i+1][i+j-1],heights[i],heights[i+j])
        #4)遍历dp矩阵搜寻最大值,heights[i:(j+1)]的矩形面积为dp[i][j]*(j-i+1)
        ans=0
        for i in range(n):
            for j in range(i,n):
                ans=max(ans,dp[i][j]*(j-i+1))
        return(ans)

下面是O(n)解法,用到了单调栈(这篇结束单调栈的文章很不错:https://zhuanlan.zhihu.com/p/26465701),这里完全按照Youtube上basketwang的解法(链接)(https://www.youtube.com/watch?v=KkJrGxuQtYo)。思路是对于每根柱子idx,找到包含它所有部分的最大矩形(idx),那么所求的柱状图的最大矩形一定等于max{最大矩形(idx)}。重点:!!!对于柱子idx,包含它所有部分的最大矩形(idx)的左右两边柱子的高度一定要大于等于柱子idx的高度!!!那么接下来的任务就是找到矩形(idx)的左右两边的柱子,矩形(i)的面积即为heights[idx](矩形的高)乘以左右边界的距离(矩形的宽)。矩形i的左/右边界其实就是heights向量里idx前/后第一个比heights[idx]小的后(left)/前(right)一位数,如下图所示:

    1            
    1   1   1    
    1   1   1    
...

 

1 ... 1 ... 1   ...
    1   1   1 1  
  1 1   1   1 1  
  1 1   1   1 1  
  left left+1   idx   right-1 right  

如果我们用单调递增栈(顾名思义,就是heights[栈顶元素]>=heights[栈底元素]),

1)建立单调递增栈:如果heights[栈顶元素+1]>=heights[栈顶元素],我们就把(栈顶元素+1) push进栈,以此来维护单调递增性;

2)寻找left和right指针:当我们遇到heights[栈顶元素+1]<heights[栈顶元素]时,那么我们就找到right指针。那left指针在哪里呢?记住,我们的栈是单调递增栈,也就是heights[栈顶元素]>=heights[栈顶前任一元素],所以left即为栈顶前一元素(注意这里可能有重复数字的情况,可以用一个while循环跳过重复数字,见代码);

3)计算当前栈顶元素的最大矩形面积:矩形(栈顶元素)的面积=heights[栈顶元素]*[right-(left+1)];

4)计算下一个栈顶元素的最大矩形面积:此时矩形(栈顶元素)的面积已经计算好了,接下来把它pop出来,计算下一个栈顶元素的最大矩形面积,这时候right指针不要移动,因为它依然有可能是最大矩形的右边界;

5)最后处理剩余部分:当heights的所有元素都进过栈(有些有可能已经出栈)之后,栈还不为空的话,那么剩下的元素的最大矩形的右边应该是mystack[-1]+1,左边依然是它的前一元素

class Solution(object):
    def largestRectangleArea(self, heights):
        """
        :type heights: List[int]
        :rtype: int
        """
        n=len(heights)
        #判断极端值
        if n==0: return(0)
        #初始化单调栈、右指针、最后需要返回的答案
        mystack=[]
        right,ans=0,0
        #进入循环
        while right<n:#右指针不能超过heights的长度
            #维护单调递增性
            if len(mystack)==0 or heights[right]>=heights[mystack[-1]]:
                mystack.append(right)
                right+=1
            #寻找left、right指针
            else:
                #进入else的时候,我们已经找到了right指针的位置,因为这时候heights[right]<heights[mystack[-1]]
                #pop出栈顶元素并记录,用于之后计算面积
                idx=mystack.pop()
                #当有重复数字的时候,我们可以跳过之前的重复数字,因为这些坐标对应的最大矩形是一样的
                while len(mystack)>0 and heights[idx]==heights[mystack[-1]]: mystack.pop()
                #当栈不为空的时候,left为当前栈的栈顶元素,也是idx没有pop出来之前,栈顶前一元素
                #当栈为空时,heights左边没有比heights[idx]更小的数,由于left为矩形左边界的前一位,所以应该是-1
                #为什么当栈为空时要单独讨论?因为我们的栈里所有元素都大于0
                #是不是初始化栈的时候把-1放入栈,这里就可以不单独讨论?不是!一来下一次矩形的高度会取到heights[-1],这是不对的高度;二来下一次栈依然为空,left取不到mystack[-1]会报错
                left=-1 if len(mystack)==0 else mystack[-1]
                ans=max(ans,heights[idx]*(right-left-1))
                #注意此处没有更新right
        #如果栈为空,那么所以元素已经处理完,返回结果
        if len(mystack)==0: return(ans)
        #否则,这时候栈是单调的,所有剩下矩形的右边界应该是栈顶元素+1
        right=mystack[-1]+1
        while mystack:
            idx=mystack.pop()
            left=-1 if len(mystack)==0 else mystack[-1]
            ans=max(ans,heights[idx]*(right-left-1))
        return(ans)

猜你喜欢

转载自www.cnblogs.com/Leisgo/p/11696176.html
今日推荐