今天回顾了一道题目
给定 n 个非负整数表示每个宽度为 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 个单位的雨水(蓝色部分表示雨水)。
解决这个问题的想法很简单,求出每个柱子上方有多少雨水,最后累加即可。而一个柱子上方有多少雨水,取决于自身的高度,以及左边柱子高度的最大值和右边柱子高度的最大值。
这样,动态规划的方法就不难理解,从左往右得到每根柱子左侧柱子高度的最大值,再从右往左得到每根柱子右侧柱子高度的最大值,最后从左往右计算每个柱子上方有多少雨水。代码如下
class Solution:
def trap(self, height: List[int]) -> int:
if not height:
return 0 # 没有柱子存不了雨水
= len(height)
n = [height[0]] + [0] * (n - 1)
leftMax for i in range(1, n):
= max(leftMax[i - 1], height[i]) #从左往右得到左侧柱子高度的最大值
leftMax[i]
= [0] * (n - 1) + [height[n - 1]]
rightMax for i in range(n - 2, -1, -1):
= max(rightMax[i + 1], height[i]) #从右往左得到每根柱子右侧柱子高度的最大值
rightMax[i]
= sum(min(leftMax[i], rightMax[i]) - height[i] for i in range(n))
ans return ans
但这种方式效率很低,其实回顾思路发现,一个柱子上方的雨水,取决于左边柱子高度的最大值和右边柱子高度的最大值。如果知道哪测比较高,那就只会用到另外一侧的最大值。所以可以改用双指针用来代替前面的数组。代码如下:
class Solution:
def trap(self, height: List[int]) -> int:
= 0
ans = 0, len(height) - 1
left, right = rightMax = 0
leftMax
while left < right:
= max(leftMax, height[left])
leftMax = max(rightMax, height[right])
rightMax if height[left] < height[right]: # 如果无法理解这个条件,可以换成 leftMax < rightMax
+= leftMax - height[left]
ans += 1
left else:
+= rightMax - height[right]
ans -= 1
right
return ans
当时阅读leetcode官方的题解有如下的描述
如果 height[left] ≥ height[right],则必有 leftMax ≥ rightMax
这个推论对我而言并不容易理解,但可以使用结构归纳法证明。
证明
假设有height 的数组,初始状态是left, right = 0, len(height) - 1。很显然,leftMax是height[0],rightMax是height[-1],一定成立。
然后关键的就是递推情况。假设 现在是left == l
, right == r
,而height[l] ≥ height[r],需要证明此时leftMax ≥ rightMax。
达到left == l
和right == r
的上一个状态有2种。
从 right == r + 1
而来
在这种情况下,不难得出leftMax ≥ min(height[r + 1],…,height[n - 1])。而height[l] ≥ height[r] , 所以leftMax ≥ height[r],最后leftMax ≥ rightMax。
从 left == l - 1
而来
此时根据推论,min(height[0],…,height[l - 1]) ≤ rightMax,而height[l] ≥ height[r]。
这个状态下其实 height[r] == rightMax
。我们可以用反证法证明。
如果height[r] != rightMax
,那么rightMax > height[r]
, min(height[0],...,height[l - 1]) ≤ rightMax == min(height[r + 1],...,height[n - 1])
. 此时right 应该大于 r,在右侧柱子高度最大值所在的索引(否则,之前right在r的时候应该继续往左移动),矛盾。
证明height[r] == rightMax
以后,由于height[l] ≥ height[r],height[l] == leftMax ≥ height[r] == rightMax。证毕。