当青训营遇上码上掘金
主题4:攒青豆
题目
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
思路
- 这个题和LeetCode42是一样的
- 我的思路是看每一列能装多少青豆再累加
- 最边上的左右两列是攒不了青豆的
- 某一列能攒多少青豆的求法:先分别找出该列左右最高列高度的值max_left,max_right,在再这两个值中取小temp=min{max_left,max_right}。如果temp不大于该列的高度h,那么这一列就攒不了青豆(就像题目中的第五列);如果temp大于了该列的高度h,那么这一列攒的青豆数就是temp-h。
- 例如第四列,max_left=5, max_right=4, temp=min{max_left,max_right}=4,这时4大于第四列的高度h=1,所以再第四列能攒的青豆数为temp-h=3
- 那么怎么去求max_left和max_right呢,难道要每次去遍历该列的左右得到吗?这样的时间复杂度会是O(n^2)。这里可以用到简单的动态规划来优化。
- 以求max_left为例。它前边的墙的左边的最高高度和它前边的墙的高度选一个较大的,就是当前列左边最高的墙了。如果我们把所有的值记录在一个left数组中,那么就有:left[i]=max{left[i-1],height[i-1]},右边同理。
- 当我们在实际写代码时发现max_left其实可以在计算每一列可以攒多少青豆时同步计算,这样就不用开辟left数组了,只需要用一个值实时记录就可以了。
- 对于记录右边最大值的数组,也可以优化掉,具体的思路可以看LeetCode42题的第一个精选题解(非官方),过程步步深入,一步步优化,直到最后。
代码
package main
import "fmt"
func theme4(height []int) int {
ans, left := 0, height[0]
right := make([]int, len(height), len(height))
//找到每一列右边最高的墙
for i := len(height) - 2; i >= 0; i-- { //right[i]=max{right[i+1],height[i+1]}
right[i] = max(height[i+1], right[i+1])
}
//从左到右判断每一列可以攒多少青豆
for i := 1; i < len(height)-1; i++ { //左右两边的两列是攒不了青豆的
// fmt.Print(left, " ")
if min(left, right[i]) <= height[i] { //如果两边最低的没有高过改列的高度的话,就攒不了青豆
left = max(left, height[i])
continue
}
ans += min(left, right[i]) - height[i]
left = max(left, height[i])
}
// fmt.Println(right)
return ans
}
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 main() {
fmt.Println(theme4([]int{5, 0, 2, 1, 4, 0, 1, 0, 3}))
}