当青训营遇上码上掘金
-
主题 4:攒青豆
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
以下为上图例子的解析:
输入:height = [5,0,2,1,4,0,1,0,3]
输出:17
解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。
具体的思路: 这道题很像刷过的部分leetcode雨水题目,看见攒青豆问题,立马想起接雨水问题。
对于下标 i,下豆子后豆子能到达的最大高度等于下标 i 两边的最大高度的最小值,下标 i 处能接的豆子量等于下标 i 处的豆子能到达的最大高度减去 height[i]。
朴素的做法是对于数组 height 中的每个元素,分别向左和向右扫描并记录左边和右边的最大高度,然后计算每个下标位置能接的豆子量。假设数组 height的长度为 n,该做法需要对每个下标位置使用O(n) 的时间向两边扫描并得到最大高度,因此总时间复杂度是 O(n2)。
上述做法的时间复杂度较高是因为需要对每个下标位置都向两边扫描。如果已经知道每个位置两边的最大高度,则可以在 O(n)的时间内得到能接的豆子总量。使用动态规划的方法,可以在 O(n)的时间内预处理得到每个位置两边的最大高度。
创建两个长度为 nn 的数组 leftMaxleftMax 和 rightMaxrightMax。对于 0≤i<n,leftMax[i] 表示下标 i 及其左边的位置中,height的最大高度,rightMax[i] 表示下标 i 及其右边的位置中,height 的最大高度。
的确, leftMax[0]=height[0],rightMax[n−1]=height[n−1]。两个数组的其余元素的计算如下:
当 1≤i≤n−1 时,leftMax[i]=max(leftMax[i−1],height[i]);
当 0≤i≤n−2 时,rightMax[i]=max(rightMax[i+1],height[i])。
因此可以正向遍历数组 height得到数组 leftMax的每个元素值,反向遍历数组 height得到数组 rightMaxrightMax 的每个元素值。
在得到数组 leftMax和 rightMax的每个元素值之后,对于 0≤i<n,下标 i处能接的豆子量等于 min(leftMax[i],rightMax[i])−height[i]。遍历每个下标位置即可得到能接的豆子总量。
附上对应得Python代码:
import sys
def trap(height: List[int]):
# 边界条件
if not height: return 0
n = len(height)
maxleft = [0] * n
maxright = [0] * n
res = 0
# 初始化
maxleft[0] = height[0]
maxright[n-1] = height[n-1]
# 设置备忘录,分别存储左边和右边最高的柱子高度
for i in range(1,n):
maxleft[i] = max(height[i],maxleft[i-1])
for j in range(n-2,-1,-1):
maxright[j] = max(height[j],maxright[j+1])
# 一趟遍历,比较每个位置可以存储多少豆子
for i in range(n):
if min(maxleft[i],maxright[i]) > height[i]:
res += min(maxleft[i],maxright[i]) - height[i]
return res
if __name__ == "__main__":
sys.stdin.readline()
height = str([5,0,2,1,4,0,1,0,3])
height = height[1: -1]
height = list(map(int, height.split(",")))
res = trap(height)
if res == -1:
print("this is a input Error")
else:
print(res)