给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例 2:
输入:height = [4,2,0,3,2,5]
输出:9
提示:
n == height.length
1 <= n <= 2 * 104
0 <= height[i] <= 105
解法:单调栈
单调递增栈:单调递增栈就是从栈底到栈顶数据是从大到小,每当遇到比栈顶大的元素就弹栈
单调递减栈:单调递减栈就是从栈底到栈顶数据是从小到大,每当遇到比栈顶小的元素就弹栈
从名字也可以看出, 它最大的特点就是单调, 也就是栈中的元素要么递增, 要么递降, 如果有新的元素不满足这个特点, 就不断的将栈顶元素出栈, 直到满足为止, 这就是它最重要的思想.
本题设计的是一个单调递减栈: 维护一个单调栈, 单调栈存储的是下标, 满足从栈底到栈顶的下标对应的数组 height 中的元素递减.
我们来分析下单调栈为什么可以满足此题的要求. 首先明确一点: 只有产生凹陷的地方才能存储雨水, 那么高度一定是先减后增, 所以当我们遍历到 增 这个位置的时候, 前面的减的地方(凹陷处)一定会存储雨水, 所以我们就将凹陷处出栈, 来计算它所能存储的雨水量.
我们仔细观察示例一蓝色的部分,可以和括号匹配类比下。每次匹配出一对括号(找到对应的一堵墙),就计算这两堵墙中的水。
我们用栈保存每堵墙。
当遍历墙的高度的时候,如果当前高度小于栈顶的墙高度,说明这里会有积水,我们将墙的高度的下标入栈。
如果当前高度大于栈顶的墙的高度,说明之前的积水到这里停下,我们可以计算下有多少积水了。计算完,就把当前的墙继续入栈,作为新的积水的墙。
总体的原则就是,
当前高度小于等于栈顶高度,入栈,指针后移。
当前高度大于栈顶高度,出栈,
计算出当前墙和栈顶的墙之间水的多少,然后计算当前的高度和新栈的高度的关系,
重复第 2 步。直到当前墙的高度不大于栈顶高度或者栈空,然后把当前墙入栈,指针后移。
代码实现:
class Solution:
def trap(self, height: List[int]) -> int:
ans=0
# 当前时刻的指针
current=0
stack=[]
while current<len(height):
# 如果栈不空并且当前指向的高度大于栈顶高度就一直循环
while len(stack)>0 and height[current]>height[stack[-1]]:
# 取出要出栈的元素
top=stack.pop()
# 栈空就出去
if len(stack)==0:
break
# 两堵墙之前的距离
distance=current-stack[-1]-1
# 取当前高度与栈顶高度的最小值 减去 刚才出栈的元素的高度,这才是边界的高度
bounded_height=min(height[stack[-1]],height[current])-height[top]
# 距离*高度
ans+=distance*bounded_height
# 当前指向的墙入栈
stack.append(current)
# 指针后移
current+=1
return ans