题目
本题具体要求见Leetcode-q42。
解题思路
首先看一下结果,虽然是随机性比较高,但是整体来说,我们的这个解法速度还是可以的:
这个题的解法title是基于双指针法的解法,主要原因是双指针法并不是问题的最难点。
这道题的主要解法很简单:既然要接雨水,那么就要把整个数组分成几个小坑,每个小坑都由数值比较高的边界(edge)构成,这种edge的判定很简单,那就是左右两边的值都不比它大,那它就是边界了。
按照这种方式,用双指针法可以很快地分出各个小坑,但是这样会带来三个问题:
- 如何计算雨水的数量?
- 如果问题1用一边遍历一边计算的方法算,那么它的核心假设是,这个坑的左边界不大于右边界,这样才能顺着算下去,但是单纯判断edge并不能够确保这个假设,如何确保这个假设对每个小坑成立?
- 对于问题2,我们可以进一步遍历,获取strong-edge,如果遍历到后面有比左边界更大的edge,则给strong-edge的flag标记为True,然后可以直接计算结果,但是如果遍历到末尾都没有比左边界更大的edge,这时候怎么计算?
对于问题3,我最终的解法是,把这个子坑逆转一下,送进算法再来一遍,这样的话,就可以完美解决这个问题了,而且这种子坑往往只会有一个,因此运算复杂度也不算很高,顶多就是2*n,还是在O(n)的范畴。
代码
class Solution:
def trap(self, height: List[int]) -> int:
return trap1(height)
def trap_edge(height: list, ind: int) -> bool:
if ind >= len(height):
return False
left = 0 if ind == 0 else height[ind - 1]
right = 0 if ind == len(height) - 1 else height[ind + 1]
if height[ind] >= left and height[ind] >= right:
return True
else:
return False
def trap1(height: list) -> int:
fast = 0
rain_total = 0
while(fast < len(height)):
if trap_edge(height, fast):
slow = fast
# print(slow)
rain_trap_total = 0
fast += 1
get_edge = False
while(fast < len(height)):
if not trap_edge(height, fast):
rain_trap_total += max(0, height[slow] - height[fast])
fast += 1
else:
get_edge = True
if height[fast] < height[slow]:
rain_trap_total += max(0, height[slow] - height[fast])
strong_right = False
right_edge = fast
fast += 1
else:
strong_right = True
break
if get_edge and (not strong_right):
sub_height = height[slow:(right_edge+1)]
sub_height.reverse()
rain_trap_total = trap1(sub_height)
if get_edge:
rain_total += rain_trap_total
if fast == len(height):
break
else:
fast += 1
return rain_total
然而看完官方解法之后,感觉猛拍大腿啊。。。是自己对问题的分析不够透彻,一眼就想到分成各个小坑,然后就沿着小坑的思路去做了,实际上,一个点上能承载多少雨水,跟它所处的“坑”确实有关,但是跟坑里的其它点是无关的,只和坑的边界有关,也就是这个点左边和右边的最大高度,只要算出一个点左边和右边的最大高度,就可以算出它能够承载多少雨水了!
这样一建模,这个问题就比我们现在的解法更加清晰明朗了。讲讲区别:
我们的做法:把整个“建筑物”分成各个小坑,然后根据划出来的小坑来计算每个点的雨水的量,但是小坑会被大坑包含,这就导致了容易出现问题,需要更多的判断和分析(也就是strong-right)那一步;
正确的做法:首先将这个问题要解的东西定义清楚,定!义!清!楚!这很重要,定义该点上的雨水值就是左边和右边的最大值的最小值减去该点的高度,那么问题就很好解了啊。
动态规划法:直接从左往右、从右往左得到各个点对应的左最大和右最大,然后计算一把,完事;
双指针法:left=0,right=len-1,一个往右,一个往左,每一步都要更新一个左最大和一个右最大,当左最大小于右最大的时候,因为left右边的最大值一定大于等于目前的右最大,所以left上的雨水值就是左最大减去当前高度,然后left就可以更新了,right也是一样的,妙!双指针真是永远滴神!
双指针法的代码:
class Solution:
def trap(self, height: List[int]) -> int:
if len(height) <= 1:
return 0
left = 0
right = len(height) - 1
leftMax = height[left]
rightMax = height[right]
trap_all = 0
while(left < right):
leftMax = max(leftMax, height[left])
rightMax = max(rightMax, height[right])
if leftMax < rightMax:
trap_all += leftMax - height[left]
left += 1
else:
trap_all += rightMax - height[right]
right -= 1
return trap_all