文章目录
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/4587afdb4a64fed5499a41fafdcdd311.png#pic_center)
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/0d964c0238ae0f90433c28e86d66b443.png#pic_center)
思路一:暴力法
对于每一个高度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语句结束,此时大循环也结束。
综上,stack
和heights
中添加的元素对结果没有影响,起到了帮助计算的作用。