42. 接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
这个题首先看你都不知道怎么解决,甚至你使用暴力方法可以都有一点懵。
如果我们按照一列一列去看的话,我们可以看出一点门道,那就是如果能接住雨水,与当前下标的柱子,当前下标的前一个柱子和当前下标的后一个柱子的高度很相关。那么这个直觉对不对呢。其实只是一个表象。你需要知道当前柱子左边比它大的元素以及当前注意右边比它的元素。这就回到了我们单调栈的范围了。那么对于单调栈,我们还是明确以下几点
1. 单调栈里面的元素是什么?
单调栈的元素可以存放我们的下标,也可以存放我们真实的高度。
这里我们单调栈就直接放下标吧
2. 单调栈里面的顺序是什么?
我们是求比目标元素大的,所以是单调递增的,从栈头到栈底
另外,我们对这个的求法是,对于当前遍历到的柱子,我们是要求它左边的最大元素,还有右边的最大元素。然后取这之间最小的再减去当前柱子的高度,在来计算体积。
那么单调栈进入元素的操作如下:
1. 如果当前元素小于栈顶元素
入栈
2. 如果当前元素等于栈顶元素
pop在入栈。因为我们要求的是靠右边的柱子,来计算宽度。
3. 如果当前元素大于栈顶元素,此时就出现凹槽了
那么,此时就需要弹出栈顶元素,弹出的栈顶元素就是凹槽的底部,下标我们设置为mid,
此时栈中的栈顶元素就是凹槽的左边位置,而当前遍历的元素就是凹槽的右边位置,此时我们就可以通过这三个元素来进行体积的计算。对于单调栈,我们并不是按照列来计算的,是按照行。
class Solution:
def trap(self, height: List[int]) -> int:
stack = [0]
res = 0
for i in range(1, len(height)):
if height[i] < height[stack[-1]]:
stack.append(i)
elif height[i] == height[stack[-1]]:
stack.pop()
stack.append(i)
else:
while stack and height[i] > height[stack[-1]]:
mid = stack[-1]
stack.pop()
if stack:
h = min(height[stack[-1]], height[i]) - height[mid]
w = i - stack[-1] - 1
res += h * w
stack.append(i)
return res
接雨水使用双指针
如果我们按照列进行计算结雨水,我们需要知道当前柱子中左边柱子的最高高度和右边柱子的最高高度,就可以计算当前位置的雨水面积。
当前列雨水面积:min(左边柱子的最高高度,记录右边柱子的最高高度) - 当前柱子高度。
为了得到两边的最高高度,我们可以用双指针进行遍历,每到一个柱子都向两边遍历一遍,这其实是有重复计算的。我们把每一个位置的左边最高高度记录在一个数组上(maxLeft),右边最高高度记录在一个数组上(maxRight),这样就避免了重复计算。
当前位置,左边的最高高度是前一个位置的左边最高高度和本高度的最大值。
即从左向右遍历:maxLeft[i] = max(height[i], maxLeft[i - 1]);
从右向左遍历:maxRight[i] = max(height[i], maxRight[i + 1]);
class Solution:
def trap(self, height: List[int]) -> int:
maxLeft = [0] * len(height)
maxRight = [0] * len(height)
maxLeft[0] = height[0]
maxRight[-1] = height[-1]
res = 0
for i in range(1, len(height)):
maxLeft[i] = max(height[i], maxLeft[i-1])
for i in range(len(height)-2, -1, -1):
maxRight[i] = max(height[i], maxRight[i+1])
for i in range(0, len(height)):
res1 = min(maxLeft[i], maxRight[i]) - height[i]
if res1 > 0:
res += res1
return res
84.柱状图中最大的矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
这个题跟接雨水类似,都是矩阵计算,那么这个题不一样的点在什么地方呢?
首先对于矩阵求面积,单个矩阵也可以是面积,如果有跨度,那么其实两个矩阵之间高度最小的矩阵作为高度,矩阵之间的下标差作为宽度,这样来计算面积。
那么我们单调栈的规则是什么呢?
1. 单调栈里面存的元素
咱们是存下标,好来定位宽度
2. 单调栈里面的顺序?
其实我们也看到了,我们是遇到小的矩阵开始进行计算
也就是意味着顺序从栈头到栈底是递减的。栈头就是最大元素。
单调栈的进栈规则:
1. 如果当前元素大于栈顶元素
进栈
2. 如果当前元素等于栈顶元素
也应该进栈,这里不pop,为什么,我们算的矩阵面积
比如 556 56 的面积 10 最前面面一个5 6的面积是15.
3. 如果当前元素小于栈顶元素
其实就应该出栈了。
取出栈顶元素,pop掉
然后计算宽度和高度,算面积
而且是while循环,直到碰到小于当前元素的。
在入栈进去当前元素
难就难在本题要记录记录每个柱子 左边第一个小于该柱子的下标,而不是左边第一个小于该柱子的高度。
此时大家应该可以发现其实就是栈顶和栈顶的下一个元素以及要入栈的三个元素组成了我们要求最大面积的高度和宽度.
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
stack = [0]
heights.insert(0, 0)
heights.append(0)
res = 0
for i in range(1, len(heights)):
if heights[i] > heights[stack[-1]]:
stack.append(i)
elif heights[i] == heights[stack[-1]]:
stack.pop()
stack.append(i)
else:
while stack and heights[i] < heights[stack[-1]]:
mid = stack[-1]
stack.pop()
if stack:
h = heights[mid]
left = stack[-1]
w = i - left - 1
res = max(h*w, res)
stack.append(i)
return res
这里在初始化加开头和末尾加了0,目的在于让他能够执行else后面的逻辑,如果这个数组本来就是单调递增的,那就不会执行到else的逻辑。