当青训营遇上码上掘金
题目描述
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
解题思路
根据题意,我们可以将数组中的每个元素看成一个柱子,两个柱子间就可以组成一个盛雨水的容器。我们可以通过左右两个双指针来表示这个容器,初始化的容器则由最左边的柱子和最右边的柱子组成(最宽),然后再根据数组中的元素去细化容器的值。我们通过以下两种情况来具体分析。
每一列可以贡献的雨水 ,由其左右两侧最矮的那个墙高来决定
可以使用双指针(two pointer),分别从两侧开始遍历,并记录对于i点而言,左右两侧的最大墙高是多少 游标从左往右
leftH[i] = max(leftH[i-1], height[i-1]) //dp 游标从右往左
rightH[i] = max(rightH[i+1], height[i+1]) //dp 从左向右(or 右向左均可)
water[i] = min(leftH[i], rightH[i])-height[i]
从解法上看需要遍历三轮,那么时间复杂度为O(3n),已经可以视为是线性的了。可不可以再优化一下呢?
由于最后一次water[i]的遍历是必须的,那么无论它从左遍历,还是从右遍历,都可以得到leftH数组或rightH数组。
所以,可以将时间复杂度优化到O(2n),空间复杂度至少需要保存一个leftH或一个rightH,因此O(n)少不了。而另一边跟着water[i]一起的,因为只需要记录一个值,所以就不用再开数组了。
代码
func bean(height []int) int {
n := len(height)
if n < 3 {
return 0
}
rightH := make([]int, n)
rightH[n-1] = height[n-1]//右侧最高的墙设为跟自己等高
for i := n-2; i >= 0; i-- {
rightH[i] = max(rightH[i+1], height[i+1])
}
leftH := height[0]
sum := 0
for i := 1; i < n; i++ {
water := min(leftH, rightH[i]) - height[i]
//fmt.Println(i, height[i], leftH, rightH[i], water)
if water > 0 {
sum += water
}
leftH = max(leftH, height[i])
}
return sum
}
func max(a,b int) int {
if a > b {
return a
}else{
return b
}
}
func min(a, b int) int {
if a < b {
return a
}else{
return b
}
}