题目:
题目链接: https://leetcode-cn.com/problems/trapping-rain-water/
解题思路:
方法一:动态编程
每个位置可以保存的水量,是当前位置左右两边最大高度的较小值,减去当前高度
所以只要知道每个位置的左右最大高度,就可以计算出当前位置的储水量
但是如果暴力每次都遍历当前位置的左右最大高度,效率过低
所以可以在遍历前,先预处理每个位置的左右最大高度,再遍历的时候,就不用每次都向左右遍历完所有数据了
在预处理每个位置的左右最大高度时,使用dp的思想:
- index = 0时,左边的最大高度是自己
- index > 0时,左边的最大高度是max(index - 1, index)
- 右边最大高度同理,只是修改为从后向前遍历
具体图示如下:
每个位置右边的最大高度如下图所示(黄色格子部分为每个柱子的高度,蓝色部分为最高的右边柱子值):
每个位置左边的最大高度如下图:
两幅图的重叠区域为:
此部分即为面积
方法二:栈
在遍历时维护一个栈,如果当前位置的柱子高度小于栈顶柱子高度,则将当前柱子高度入栈;如果大于栈顶柱子高度,则将栈中的元素依次出栈计算储水量,图示如下:
遍历开始,第一个元素直接入栈(栈中保存的是数组下标,用于计算长度)
第二个元素入栈前,发现比前一个柱子高度高,此时先将上一个柱子出栈,判断上一个柱子左右两个柱子的高度
此时发现没有左边的柱子,所以不进行面积的计算,直接将1入栈,继续下次循环
继续遍历到下标为3的位置,发现3比2的柱子高度要高
此时,在3入栈前,对3之前的有效面积进行计算,计算逻辑为:每个柱子的左右最低高度,减去当前柱子的高度,乘以长度
对于下标为2的位置来说,具体计算式子如下:
- 左右最低高度min_height = min(height[3], height[1]) = min(2, 1) = 1
- 当前高度current_height = height[2] = 0
- 长度length = 3 - 1 - 1 = 1
- 有效面积area = (min_height - current_height) * length = (1 - 0) * 1 = 1
继续对1进行判断时,发现栈中1左边没有柱子了,此时停止计算有效面积,将3入栈,继续遍历:
遍历到下标为6时,比栈顶元素5的柱子高度高,继续按照上述逻辑计算面积后,增加面积为1,之后栈的情况如下:
此时栈顶的下标为4,但是6的高度与4的高度相同,停止对栈进行遍历,将6入栈后,继续遍历:
遍历到7时,7的高度比6的高,再次开始对栈的遍历,首先对6在栈中左边的柱子高度和当前位置7的进行比对,逻辑如下:
- 左右最低高度min_height = min(height[4], height[7]) = min(1, 3) = 1
- 当前高度current_height = height(6) = 1
- 长度length = 7 - 4 - 1 = 2
- 有效面积area = (min_height - current_height) * length = (1 - 1) * 0 = 0
此时将6出栈后,继续对栈顶元素判断,发现7的高度比4的高度高,继续对栈进行遍历,此时栈的情况如下:
此时对4位置的计算逻辑如下:
- 左右最低高度min_height = min(height[3], height[7]) = min(2, 3) = 2
- 当前高度current_height = height(4) = 1
- 长度length = 7 - 3 - 1 = 3
- 有效面积area = (min_height - current_height) * length = (2 - 1) * 3 = 3
此时栈情况如下:
按照此逻辑继续遍历,直到遍历完所有柱子
方法三:双指针
left头指针,right尾指针双指针遍历,当前位置的面积,有下述几种情况:
- 如果height[left] > height[right],当前位置的面积由height[right]决定
- 其他情况,由height[left]决定
具体提示流程图如下:
变量如下:
- left_max: 当前遍历的左边最大高度,初始化为0
- right_max: 当前遍历的右边最大高度,初始化为0
- left: 左指针,初始化为0
- right: 右指针,初始化为len(height) - 1,即最后一个元素下标
- area: 累加面积,初始化为0
初始位置如下:
此时发现height[right] > height[left],计算left处的面积,逻辑如下:
- 比对height[left]和left_max大小,发现此时height[left] = 0, left_max = 0,无须计算面积
- 向右移动left = left + 1
处理完后的指针情况如下:
left和right相等,此时处理逻辑如下:
- 比对height[left]和left_max大小,发现height[left] > left_max,更新left_max = height[left],不计算面积
- 向右移动left(或者向左移动right,道理相同)
处理完后的情况如下:
此时处理逻辑如下:
- height[left] < left_max,计算left处的面积 = left_max - height[left] = 1 - 0 = 1
- 继续向右移动left
处理完成结果及后续图示如下:
代码实现:
方法一:
class Solution:
def trap(self, height: List[int]) -> int:
if 0 == len(height):
return 0
left_max, right_max = [0] * len(height), [0] * len(height)
left_max[0] = height[0]
right_max[-1] = height[-1]
for index in range(1, len(height)):
left_max[index] = max(height[index], left_max[index - 1])
for index in range(len(height) - 2, -1, -1):
right_max[index] = max(height[index], right_max[index + 1])
area = 0
for index in range(len(height)):
area += min(left_max[index], right_max[index]) - height[index]
return area
方法二:
class Solution:
def trap(self, height: List[int]) -> int:
area = 0
rec = []
for index in range(len(height)):
while 0 < len(rec) and height[index] > height[rec[-1]]:
last_idx = rec[-1]
rec.pop()
if 0 == len(rec):
break
distance = index - rec[-1] - 1
tmp_height = min(height[index], height[rec[-1]]) - height[last_idx]
area += distance * tmp_height
rec.append(index)
return area
方法三:
class Solution:
def trap(self, height: List[int]) -> int:
left_max, right_max = 0, 0
left, right = 0, len(height) - 1
area = 0
while left < right:
if height[left] > height[right]:
if height[right] >= right_max:
right_max = height[right]
else:
area += right_max - height[right]
right -= 1
else:
if height[left] >= left_max:
left_max = height[left]
else:
area += left_max - height[left]
left += 1
return area