给定n个非负整数,代表一个高程地图,每个整数表示一个挡板,挡板高度为整数值,挡板宽度均为1(即挡板都是紧挨的,之间没有缝隙)。
计算下雨之后,整体可以存储多少水。
示例:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:高度图如下:
那么下雨时,两个柱子之间如果有低谷,则会存储雨水,如下:
有 A、B、C三个低谷可以存储雨水,雨水总面积为 1+4+1=6。
思路
1、分治法
寻找到最高的两个柱子,他们两个之间如果还有其他柱子,则这些柱子高度都不高于他们两个,肯定可以蓄水。
可以根据两根柱子之间的较低高度以及两根柱子的距离,计算出蓄水的面积。注意,两根柱子之间的低位柱子们,相当于水底的小山丘,占据了水的体积,需要减掉。
然后,这两个柱子,将整个地图分成了三部分,中间这一部分刚刚已经计算过,使用递归继续处理剩下的两个水池。
最后,三个水池的蓄水面积相加即可。
每次递归需要找到高度最高的两根柱子,其算法复杂度为 O(n);计算两根柱子之间的蓄水面积,算法复杂度为 O(n);递归次数为 O(logn)。因此,整体算法复杂度为 O(nlogn)。
2、双指针法
维护左右两个指针,作用是从首尾两端出发向中间进行遍历。
维护左右两个历史最高纪录,分别指向已扫描过的柱子中的最高柱子,作用是与当前指针计算高度差,高度差即为这根柱子上面的水的体积。
具体步骤如下:
- 比较两个指针的柱子高度,选择较低者,比如为左指针(以下步骤全部按照左指针为较低者处理,右指针同理)。
- 比较左指针和左最高柱子:
(1)如果左指针的高度<左最高柱子,则说明左指针这根柱子上面肯定有蓄水(因为左指针这根柱子的右边肯定至少有一个柱子,即当前右指针,可以挡着它的蓄水,这也是第一步要选择较低者的原因),蓄水的高度即为(左最高柱子高度-左指针高度)。
(2)如果左指针的高度>=左最高柱子,则说明上面肯定没有蓄水,因为左边没有柱子可以挡着水。这时需要更新左最高柱子为当前左指针。 - 左指针+1。
- 重复上面三步,直至左右指针相遇,算法结束。
python实现
import matplotlib.pyplot as plt
def trap(height):
"""
:type height: List[int]
:rtype: int
分治法
"""
l = len(height)
if l <= 2:
return 0
# 1、寻找两个最高的柱子
max1_idx = 0 if height[0] > height[1] else 1 # 最大值的索引
max2_idx = 1 - max1_idx # 第二大值的索引
for i in range(2, l):
h = height[i]
if h > height[max1_idx]:
max2_idx = max1_idx
max1_idx = i
elif h > height[max2_idx]:
max2_idx = i
# 2、计算两个柱子之间的水量
# 获取较低的高度
small_height = min(height[max1_idx], height[max2_idx])
if small_height == 0: # 较低高度为0,说明全场只有一根柱子有高度,存不了水
return 0
left_idx = min(max1_idx, max2_idx)
right_idx = max(max1_idx, max2_idx)
# 先计算两根柱子之间的容量
middle_vol = max((right_idx - left_idx - 1) * small_height, 0)
# 再减去之间的其他柱子所占据的体积
middle_vol -= sum(height[left_idx + 1 : right_idx])
# 3、递归计算剩余部分的水量
return middle_vol + trap(height[:left_idx+1]) + trap(height[right_idx:])
def trap2(height):
"""
:type height: List[int]
:rtype: int
双指针法
"""
left,right = 0, len(height)-1
maxleft , maxright= 0,0
ret = 0
while left <= right:
if height[left] <= height[right]: # 左边低于右边
if height[left] < maxleft: # 左边当前高度小于左边最大高度
ret += maxleft - height[left] # 高度查为当前柱子上面的水量
else: # 左边当前高度大于左边最大高度
maxleft = height[left] # 更新左边最大高度
left += 1
else: # 右边低于左边
if height[right] < maxright:
ret += maxright - height[right]
else:
maxright = height[right]
right -= 1
return ret
if '__main__' == __name__:
height = [0,1,0,2,1,0,1,3,2,1,2,1]
# 画出柱状图
X = list(range(0, len(height)))
Y = height
plt.bar(X, Y, 1)
plt.ylim(0, max(Y)+1)
for a,b in zip(X,Y):
plt.text(a, b+0.05, '%.0f' % b, ha='center', va= 'bottom',fontsize=11)
plt.show()
print(trap2(height))