1 题目
给定 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
2 分析
首先,从图示可以看出,数组总的接水量等于每个位置接水量的和。而每个位置的接水量取决于它左右两边柱子的高度(准确的说,是取决于它左边最高的柱子和右边最高的柱子中较低的那个)。这样它左右两边和它自己就可以形成一个容器来盛水。因此,最暴力的解法是遍历每一个位置i,然后从这个位置出发,分别向左右两边寻找最高的柱子leftMax和rightMax,然后计算得到盛水量min(leftMax,rightMax)-heighti。
python
def trap(self, height):
"""
:type height: List[int]
:rtype: int
"""
n = len(height)
res = 0
for i in range(1,n-1):
leftMax = max(height[:i])
rightMax = max(height[i+1:])
tmp = min(leftMax,rightMax)-height[i]
if tmp>0:
res += tmp
return res
但上面这种方式时间复杂度很高,而且其实有很多重复工作。例如,对于第i+1个位置而言,其左边的leftMax其实只需要看第i个位置左边的leftMax与heighti的大小。
python
def trap(self, height):
"""
:type height: List[int]
:rtype: int
"""
n = len(height)
res = 0
leftMax = height[0]
for i in range(1,n-1):
leftMax = max(height[i-1],leftMax)
rightMax = max(height[i+1:])
cur = min(leftMax,rightMax)-height[i]
if cur>0:
res += cur
return res
但依然超出时间限制,因为右边rightMax还是有重复工作。因此,考虑把leftMax的递归思想引入rightMax中,先从右向左计算每个位置的rightMax并用数组记录,后续再遍历计算每个位置的雨水量的时候,就可以直接取这部分信息使用了。
python
def trap(self, height):
"""
:type height: List[int]
:rtype: int
"""
n = len(height)
res = 0
leftMax = height[0]
rightMax = height[n-1]
rightMaxList = [0]*n
for i in range(n-1,0,-1):
rightMax = max(height[i],rightMax)
rightMaxList[i] = rightMax
for i in range(1,n-1):
leftMax = max(height[i-1],leftMax)
rightMax = rightMaxList[i+1]
cur = min(leftMax,rightMax)-height[i]
if cur>0:
res += cur
return res
上面的方法提交成功,终于不超时了,但只击败了5%。需要思考下还有没有别的优化点。
有一个关键点是,每次计算位置i的盛水量,其实只用到了leftMax和rightMax中较小的那个。如果我们明确知道位置i的右边有个很大的值,超出了其左边的最大值,那么就可以只用leftMax的信息。同理,如果我们明确知道位置i的左边有个很大的值,超出了其右边的最大值,那么就可以只用rightMax的信息。而每一个时刻,leftMax和rightMax总有大小,因此总有位置的盛水量可以被计算,不是左边就是右边。
换一种方式说,如果有两个指针left和right分别从左向右、从右向左移动,leftMax表示height:left的最大值,heightright:的最大值。那么,如果明确leftMax<rightMax,其实已经可以更新left位置的盛水量了(因为rightMax只会增大不会减少了:因为left<right,从而heightleft:>=heightright:)。同理,如果明确leftMax>rightMax,其实已经可以更新right位置的盛水量了(因为leftMax只会增大不会减少了:因为left<right,从而height:right>=height:left)。如果明确leftMax=rightMax,那么更新left和right都可以,因为它俩都有一边是会只增不减的。这样我们就可以一边移动双指针,一边更新盛水量,一遍维护leftMax和rightMax了。
python
def trap(self, height):
"""
:type height: List[int]
:rtype: int
"""
n = len(height)
res = 0
left, right = 1, n-2
leftMax = height[0]
rightMax = height[n-1]
while left <= right:
if leftMax < rightMax:
cur = leftMax-height[left]
leftMax = max(height[left],leftMax)
left += 1
res += max(cur,0)
else:
cur = rightMax-height[right]
rightMax = max(height[right],rightMax)
right -= 1
res += max(cur,0)
return res