解法一:按行算:
整个思路就是,求第 i
层的水,遍历每个位置,如果当前的高度小于 i
,并且两边有高度大于等于 i
的,说明这个地方一定有水,水就可以加 1。
如果求高度为 i
的水,首先用一个变量 temp
保存当前累积的水,初始化为 00。从左到右遍历墙的高度,遇到高度大于等于 i
的时候,开始更新 temp
。更新原则是遇到高度小于 i
的就把 temp
加 1,遇到高度大于等于 i
的,就把 temp
加到最终的答案 ans
里,并且 temp
置零,然后继续循环。
我们就以题目的例子讲一下。
先求第一行的水
再求第 2 行的水。
如此类推。
代码:
def solution(l):
max_height = max(l)
sum = 0
for y in range(1,max_height+1):
update_flag = False
temp = 0
for x in range(len(l)):
if l[x] >= y:
update_flag = True
sum += temp
temp = 0
if l[x]<y and update_flag ==True:
temp+=1
return sum
print(solution([0,1,0,2,1,0,1,3,2,1,2,1])) #6
时间复杂度:如果最大的数是 m,个数是 n,那么就是 O(m*n)。
空间复杂度:O(1)。
解法二:按列算:
求每一列的水,我们只需要关注当前列,以及左边最高的墙,右边最高的墙就够了。
装水的多少,当然根据木桶效应,我们只需要看左边最高的墙和右边最高的墙中较矮的一个就够了。
所以,根据较矮的那个墙和当前列的墙的高度可以分为三种情况。
- 较矮的墙的高度大于当前列的墙的高度
- 把正在求的列左边最高的墙和右边最高的墙确定后,然后为了方便理解,我们把无关的墙去掉。
-
这样就很清楚了,现在想象一下,往两边最高的墙之间注水。正在求的列会有多少水?
很明显,较矮的一边,也就是左边的墙的高度,减去当前列的高度就可以了,也就是 2 - 1 = 1,可以存一个单位的水。
- 较矮的墙的高度小于当前列的墙的高度
- 同样的,我们把其他无关的列去掉。
-
想象下,往两边最高的墙之间注水。正在求的列会有多少水?
正在求的列不会有水,因为它大于了两边较矮的墙。
-
较矮的墙的高度等于当前列的墙的高度。
和上一种情况是一样的,不会有水。
-
代码:
def solution(l):
flag = False
sum = 0
for i in range(1,len(l)): #因为第一列肯定是没雨水的,所以从第二列开始
if l[i] == 0 and flag == False:
continue
else:
left_max = 0
right_max = 0
temp = 0
flag = True
for left in range(i):
left_max = max(left_max,l[left])
for right in range(i+1,len(l)):
right_max = max((right_max,l[right]))
shorter = min(left_max,right_max)
if shorter != l[i] and shorter>l[i]:
temp = shorter - l[i]
print('i={},shorter={},temp={},leftmax={},rightmax={}'.format(i,shorter,temp,left_max, right_max))
sum += temp
return sum
print(solution([0,1,0,2,1,0,1,3,2,1,2,1])) #6
时间复杂度:O(n²),遍历每一列需要 nn,找出左边最高和右边最高的墙加起来刚好又是一个 n,所以是 n²。
空间复杂度:O(1)。
解法三:动态规划:
动态规划是基于解法二上的,即是优化解法二的。
在解法二中,每一列都需要找出其左边d最高的柱子和右边最高的柱子,在解法二中每一个都去遍历寻找左,右最高的柱子会出现计算重复,因此用动态规划来找每一列的左,右最高的柱子。
left_max[i]=max(left_max[i-1],l[i-1]]
right_max[i]=max(right_max[i+1],l[i+1]]
上面的两条式子,就可以看出动态规划的影子,即当前状态可以由之前状态推导而出。
代码:
def solution(l):
left_max_list=[0 for _ in range(len(l))]
right_max_list = [0 for _ in range(len(l))]
sum = 0
for i in range(1,len(l)): #求出各个列表中左边最大的柱子,从1开始是因为第一个柱子肯定是没有雨水的
left_max_list[i] = max(left_max_list[i-1],l[i-1])
for i in range(len(l)-2,-1,-1): #求出各个列表中右边最大的柱子,从-2是因为最后一个柱子肯定也是没有雨水的
right_max_list[i] = max(right_max_list[i+1],l[i+1])
for i in range(len(l)):
shorter = min(left_max_list[i],right_max_list[i])
if shorter > l[i]:
sum += shorter - l[i]
return sum
print(solution([0,1,0,2,1,0,1,3,2,1,2,1])) #6
时间复杂度:O(n)。
空间复杂度:O(n),用来保存每一列左边最高的墙和右边最高的墙。
解法四:双指针
双指针是在数组的头和尾都插入一个指针。
然后两个指针会往中间移动,从而计算每列的水量。
在这道题的思路如下:
基于木桶原理,装的水量 取决于最短的一个柱子。
每次决定是左指针 left_p移动还是右指针 right_p移动时,先判断 left_p 前一个柱子高还是 right_p后一个柱子高,由于水量是取决于较低的柱子的,所以若是 left_p 后一个的柱子较低,则这轮先计算left_p柱子上的水,然后移动 left_p。反之亦然。
无论是 left_p 还是 right_p,没经过一个柱子都会记录下,经过的这些柱子中,最高的柱子的高度是多少。因为这并不是单纯的木桶问题,知道是移动 left_p 还是 right_p后,left_p或right_p位置最高能装多少水是取决于 left_p经过的最高柱子的,如下图:
若当前的left_p 和 right_p如图所示。right_p 的前一个柱子比 left_p的前一个柱子高,因此计算left_p的水量。如图很明显,left_p的最大水量应该和left_max的高度是相等的。同理,对于right_p也是一个道理。所以left_p和 right_p都必须记录一个 left_max和 right_max来保存各自方向的最高柱子的高度。
def solution(l):
sum = 0
left_max = 0
right_max = 0
left_p = 1
right_p = len(l)-2
n = len(l)
for i in range(n-1):
if l[left_p-1] < l[right_p+1]:
left_max = max(left_max,l[left_p-1]) #left_max是记录当前left位置之前的最高柱子
min = left_max
if min>l[left_p]: sum += (min - l[left_p])
left_p+=1
else:
#print(i)
right_max = max([right_max,l[right_p+1]]) ##right_max是记录当前right位置之前的最高柱子
min = right_max
if min > l[right_p]: sum += (min - l[right_p])
right_p -= 1
return sum
print(solution([0,1,0,2,1,0,1,3,2,1,2,1])) #6
时间复杂度:O(n),只需要一次循环就能完成。
空间复杂度:O(1)