题意:给定一个数组代表一个柱状图里每根柱子的高度,每根柱子宽为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)