拒绝躺平,每日一卷(第九天)
目录
题目:给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。(蓝色部分为雨水)
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
(按照常识我们也可以知道,可装6个单位的雨水)
列表长度大于等于1
方法一:动态规划(图像法)
我们知道可以盛水的最大高度取决于所找出的两根最高柱子中的较短那根,就是先找出一定范围内两根最高的柱子,盛水的最大高度等于较短那一根的高度。现在我们从先左往右遍历列表,找出从左到右最高的柱子。
以例题为例,如图:
设从左到右最大高度为 left_max,则left_max,的值分别为
left_max:0,1,1,2,2,2,2,3,3,3,3,3
序列: 0,1,2,3,4,5,6,7,8,9,10,11
得到图形有色面积s1
再从右往左遍历列表,得到从右往左最大高度right_max
right_max,的值分别为
right_max:1,2,2,2,3,3,3,3,3,3,3,3
序列: 0,1,2,3,4,5,6,7,8,9,10,11
得到有色图形面积s2
将两部分(图片)重合
其中斜杠部分便是所求盛水容量。那么如何获取斜杠部分的面积呢?
2倍斜杠部分的面积 = s1 + s2 - 2黑色部分面积 - 浅红色面积-浅绿色面积
==> 斜杠部分的面积 = s1 + s2 - 2黑色部分面积 - 浅红色面积-浅绿色面积-斜杠部分面积
==> 斜杠部分的面积 = s1 + s2 - 2黑色部分面积 -叠加后整体矩形面积
代码实现如下:
def trap(height):
ans = 0
left_max = 0
right_max = 0
for i in range(len(height)):
left_max = max(left_max, height[i]) # 从左往右,不断更新left_max最大值
right_max = max(right_max, height[-i - 1]) # 从右往左,不断更新right_max最大值
ans += left_max + right_max - height[i] # 加上上面不断更新的left_max和right_max,构成s1,s2
return ans - len(height) * left_max # 等价于ans - len(height) * right_max,其中len(height) * left_max为叠加后总矩形面积
时间复杂度O(n),空间复杂度O(n)
方法二:双指针。
顾名思义,需要放置两根指针。现在我们放两根指针,首指针为left,尾指针为right。我们可以知道,
1.当height[left] < height[right]时,盛水最大高度有可能为height[left]
这时,不断向右移动left指针,并更新left_max,将left_max - height[left],即可获取盛水量
2.当height[left] >= height[right]时,盛水最大高度有可能为height[right]
这时,不断向左移动right指针,并更新right_max,将right_max - height[right],即可获取盛水量
def trap(height):
ans = 0 # 初始化结果列表
left, right = 0, len(height) - 1 # 放置指针
left_max = right_max = 0 # 初始化
while left < right: # 当指针没有重合时循环
left_max = max(left_max, height[left]) # 更新left_max
right_max = max(right_max, height[right]) # 更新right_max
if height[left] < height[right]:
ans += left_max - height[left] # 获取盛水面积
left += 1 # 向右移动left指针
else:
ans += right_max - height[right]
right -= 1
return ans
我们以例题为例说明:
开始left = 0
left_max = height[left] = 0------>height[left] < height[right]---------->ans += left_max - height[left] = 0
left + 1
left = 1 right = 11
left_max < height[left] = 1------>height[left] = height[right]---------->ans += right_max - height[right]=0
right - 1
right = 10 left = 1
left_max = height[left] = 1------>height[left]=1 < height[right]=2---------->ans += left_max - height[left] = 1
left + 1
left = 2
到此我们就正确得出从第一个元素到第三个元素的盛水量,后面的可以此类推
方法3:动态规划
方法一的原理就是动态规划,是比较特殊的解法。那么什么是动态规划?
简单来说就是利用一些“历史记录”,一个或几个数组储存数据,以达到避免重复计算的目的。
就该题来说,想知道下标i(位置)是否可以盛水,需要判断i位置的左右最大值,对每一个 i 判断其左右位置的最大值,就可以的到该位置的盛水量。这时暴力解题的做法,可以根据动态规划的记忆性来储存从左往右最大值left_max,再储存从右往左最大值right_max。这时有个语句上的疑问就是,最大值不是固定一样的吗?为什么还分从左往右,从右往左。
这里的最大值是不断更新的,有向的最大值会覆盖较小的值。比如
height = [0,1,0,2,1,0,1,3,2,1,2,1]
从左往右 left_max:0,1,1,2,2,2,2,3,3,3,3,3
从右往左 right_max:1,2,2,2,3,3,3,3,3,3,3,3
最大值都是3,但个阶段更新的最大值是不一样的
def trap(height):
if not height: # 如果列表为空
return 0
n = len(height) # 列表长度
left_max = [height[0]] + [0] * (n - 1) # 创建一个首位置为hight[0],其他位置都为0长度为n的列表
for i in range(1, n):
left_max[i] = max(left_max[i - 1], height[i]) # 不断更新并储存最大值
right_max = [0] * (n - 1) + [height[n - 1]]
for i in range(n - 2, -1, -1):
right_max[i] = max(right_max[i + 1], height[i])
ans = sum(min(left_max[i], right_max[i]) - height[i] for i in range(n)) # 根据储存后的左右最大值列表计算面积
return ans
range(n - 2, -1, -1)是从n-2到-1,每次移动1个距离,这时对于反向遍历的写法
读别人写的程序详单与理解他人的思维模式,想要更好的理解他人的解题原理,带入简单的例子进行实操是一个不错的方法。
-------------------------------------------------------------end--------------------------------------------------------------
链接:https://leetcode-cn.com/problems/trapping-rain-water/solution/jie-yu-shui-by-leetcode-solution-tuvc/ 来源:力扣(LeetCode)