Date:
Nov. 2, 2017
Problem:
https://leetcode.com/problems/trapping-rain-water/description/
Description:
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.
The above elevation map is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. Thanks Marcos for contributing this image!
一开始想了很多扯淡的方法,其中比较可行的一种是向右遍历,检查到新高度时向右扫描这层水的体积(寻找一个大于等于这一高度的,姑且叫做“墙”吧)。
不过这种方法对于一枝独秀的高墙,对,没错,比如上面的height[8],尤其是当它是height[0]的时候,需要额外讨论很多东西。于是懒癌患者被直接劝退了。
所以修改了一下这个方法的思路,现在我们使用两个指针l(左指针)和r(右指针)来向中间扫描。
注意,直接计算水的体积十分麻烦,所以我们用一种简化的思路:
用block记录总的方块数,这只需要一次扫描。
用water记录不考虑方块占空间的水体积,这可以在接下来的扫描中计算出来。
最后,我们返回water - block,也就是水的真实体积。
我们看到,不考虑方块占空间的话,水的体积呈现出一个极好的(或者两个)实心阶梯”形状“。这使我们很方便的计算它的量。
我们用lh表示已知的左侧墙高,用rh表示已知的右侧墙高,用wh表示目前水平面高度。初始状态下,它们的值都是0。
当lh <= rh的时候,我们试图向右扫描一堵更高的左墙来装下更多的水。我们将lh的值更新为扫描到的第一堵更高的左墙。将这个过程中增加的水量加入water:
water += (r - l + 1)*(min(lh, rh) - wh)
注意,这些水是“一层一层”地“叠放”起来的。
然后更新水平面高度。
wh = min(lh, rh)
当lh > rh时,做类似向左扫描加水操作。
l,r指针交错时,返回结果。
这个算法复杂度为O(N)。
以下是submission。
1 class Solution: 2 def trap(self, height): 3 l = 0 4 r = len(height) - 1 5 lh = 0 6 rh = 0 7 wh = 0 8 water = 0 9 block = 0 10 for i in height: 11 block += i 12 while l < r: 13 if lh <= rh: 14 while l < r and height[l] <= lh: 15 l += 1 16 lh = height[l] 17 water += (r - l + 1)*(min(lh, rh) - wh) 18 wh = min(lh, rh) 19 else: 20 while l < r and height[r] <= rh: 21 r -= 1 22 rh = height[r] 23 water += (r - l + 1)*(min(lh, rh) - wh) 24 wh = min(lh, rh) 25 return max(water - block, 0)
另附“竖放”水层的算法,用C++实现。作者mcrystal。
class Solution { public: int trap(int A[], int n) { int left=0; int right=n-1; int res=0; int maxleft=0, maxright=0; while(left<=right){ if(A[left]<=A[right]){ if(A[left]>=maxleft) maxleft=A[left]; else res+=maxleft-A[left]; left++; } else{ if(A[right]>=maxright) maxright= A[right]; else res+=maxright-A[right]; right--; } } return res; } };