题目描述
这道题可以借鉴直方图中求最大矩形面积的思想,是一道非常经典的题目,难度非常大,但是又经常在面试中出现。
Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.
For example,
Given [0,1,0,2,1,0,1,3,2,1,2,1], return 6.
分析
要理解本文的方法,可以先去看看陈利人老师提出的O(n)时间内解决直方图中最大矩形面积的问题很神奇的解法!怎么求柱状图中的最大矩形? ,如果网页上面无法查看图片,可以关注陈利人老师的微信公众号,上面还有一些其他的经典面试题。
算法的核心思想在于维护了一个非递减的栈,并考虑了相邻矩形之间的高度关系,具体请查看陈利人老师原文,这里不再详细讲解,可以理解了之后再继续阅读本文,也可以直接阅读本文。我将尝试着跟大家讲清楚!这里列出陈老师文章里的一张经的图片
回到本题:
什么样的情况才可能存水呢?只有两边的都比自己高,才有可能存水!如下图所示。
但是图形肯定不是如此简单的,如下图所示
使用一个非递增的栈stack, 也就是说(遍历数组遇到height[i]
比当前栈顶元素更大的元素时,需要将栈中所有比height[i]
小的元素出栈,只有这样才能维持栈的非递增顺序),那么在这些元素出栈时如何高效的计算和存储water量呢?
逐步填平凹陷的地方,如下图所示:
上面给出了计算的顺序,下面我们来逐步解释!
给定三个重要的量:
- 当前编号
i
- 当前栈顶元素
bot=stack[-1]
- 栈顶元素出栈之后新的栈顶元素
stack[-1]
根据这三个量的变化情况演示上面的图:
stack
是非递增的,可以知道当i=7
时,stack=[2,3,4,5,6]
.此时有stack[-1]=5,bot=6,i=7
,先计算途中紫色部分;此时栈顶元素5对应的高度还是小于i=7
的,继续出栈,stack[-1]=4,bot=5,i=7
,计算绿色部分。然后7本身是最小的了,入栈i=8
入栈i=9,stack=[2,3,4,7,8]
,8出栈,计算淡紫色部分;接着7出栈,计算蛋黄色部分;然后4出栈,计算红色部分;最后3出栈,计算桃红色部分;然后2出栈,此时栈已经为空,无需再计算。即得到了总体积水量。
注意:bot始终代表stack[-1]
和i
之间已经填平后的高度(注意分析这句话),新的积水量:
(min(height[i],height[stack[−1]])−height[bot])∗(i−stack[−1]−1)
将所有的积水量相加就是最后的结果。
编码
stack = []
i = 0
maxwater = 0
while (i < len(height)):
if len(stack) == 0 or (height[i] <= height[stack[-1]]):
stack.append(i)
i += 1
else:
bot = stack[-1]
stack.pop()
if len(stack) == 0:
water = 0
else:
water = (min(height[i], height[stack[-1]]) - height[bot]) * (i - stack[-1] - 1)
maxwater += water
return maxwater
上述代码在leetcode上面并不是最优的,有兴趣的童鞋可以看看大神们的解法!下面我们跟踪一下代码,帮助理解算法,已经读懂的同学不用再看下面的!
示例
- 开始时
i=0,stack=[ ]
, 根据算法stack.append(i) i++
i=1,stack=[0]
,而栈顶元素高度小于当前i
的高度,出栈操作并且计算water=0:
当再次进入while
时,由于栈为空,直接将1
进栈,且i++
i=2,stack=[1],i=2
,当前元素高度小于栈顶元素高度,i
入栈i=3,stack=[1,2]
, 栈顶元素高度小于i
的高度(height[stack[-1]<height[i]]
),计算存水量:bot=stack.pop()
即bot=1
,栈非空:[0]
:(min(height[stack[-1]],height[i])-height[bot])*(i-stack[-1]-1)=(min(height[1],height[2])-height[2])*(2-0-1)=1
i=3,stack=[1]
,当前元素i
的高度大于栈顶,则出栈,此时栈为空,水量为0i=3,stack=[]
,直接将i
入栈,且i++
i=4,stack=[3]
,入栈且i++
i=5,stack=[3,4]
,还是入栈操作i=6,stack=[3,4,5]
,此时需要计算water了。先将5
出栈计算的雨量1.然后栈顶元素4, 该入栈了。i=7,stack=[3,4]
,大于栈顶元素,应该计算water,此时计算的是浅黄色的部分(原理我们稍后解释)i=7,stack=[3]
,出栈后为空,water=0,在下一步的while
中重新将i=7
入栈,一直继续下去…