当青训营遇上码上掘金
-
主题 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 个单位的青豆。
思路
一、动态规划
因为每一个位置可以存储的最大青豆数取决于该位置左侧和右侧边界的最高值。
比如解析中下标为1的元素,左侧的高度为5,而右侧最高值为4,则该位置可以存储的上界就是4。
因此我们需要维护一个数组,数组存储每一个位置的左侧最高和右侧最高,最后根据该位置的左侧右侧最高值中的较小值减去该位置的高度,则为该位置可以存储的青豆数了。
func trap(height []int) int {
// 0: 左侧最高 1: 右侧最高
dp := make([][]int, len(height))
// 初始化左右边界数组
for i,_ := range dp {
dp[i] = make([]int, 2)
}
dp[0][0] = height[0]
n := len(height)
dp[n-1][1] = height[n-1]
// 计算每个位置的左侧最高点
for i := 1; i < n; i++ {
dp[i][0] = max(dp[i-1][0],height[i])
}
// 计算每个位置的右侧最高点
for i := n-2; i >=0; i-- {
dp[i][1] = max(dp[i+1][1],height[i])
}
res := 0
// 计算每一个位置可容纳的最多青豆
for i := 0; i < n; i++ {
res += min(dp[i][0], dp[i][1]) - height[i]
}
return res
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
二、单调栈
另一种思路就是用单调栈来计算当前位置可容纳的最大青豆数。
我们需要维护一个单调栈,栈中元素存储下标,栈中元素下标对应的高度为递减存储,当某个位置的高度大于栈顶元素下标的高度时,说明此时有一个位置右侧高度大于左侧,可以容纳青豆。此时我们需要对栈顶元素出栈,而当前可容纳位置的计算为,新的左侧边界和当前移动下标的高度之间的最小值减去出栈元素下标的高度。
func trap(heights []int) int {
stack := []int{}
res := 0
for pos, height := range heights {
// 栈非空,且当前下标下的高度大于栈顶元素的高度
for len(stack) > 0 && height > heights[stack[len(stack)-1]] {
// 出栈栈顶元素
top := stack[len(stack)-1]
stack = stack[:len(stack)-1]
if len(stack) == 0 {
break
}
// 更新后的栈顶元素为当前左侧上界
left := stack[len(stack)-1]
width := pos - left - 1
maxHeight := min(heights[left], height) - heights[top] // 当前可容纳的高度
res += width * maxHeight
}
stack = append(stack, pos)
}
return res
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
其实问题不是很难,需要进行画图理解一下左右边界的选取即可。
人生苦短,不如go浪一下。