当青训营遇上码上掘金 我选择的主题是主题四, 现在为大家提供一下我的思路, 仅供参考.
题目要求
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
题目分析
首先将问题拆分, 需要求的是所有柱子可以容纳的青豆, 由于"木桶效应", 每根柱子容纳的青豆是其左边的最高柱子, 与右边的最高柱子的较小值(此时的柱子宽度为1). 所以很容易想到了解法一: 可以利用两个一维数组来记录数组的前缀最大值和后缀最大值, 然后取min操作, 减去i处的柱子高度就是i处可以容纳的青豆数量.
解法一
由于青训营后端使用语言是Golang, 所以本次解题思路也用Go啦, 如果有代码不规范的问题请小伙伴不吝指出.
func trap(height []int) int {
l := len(height)
p, s := make([]int, l), make([]int, l)
pMax, sMax := 0, 0
for i := 0; i < l; i++ {
if height[i] > pMax {
pMax = height[i]
}
if height[l-i-1] > sMax {
sMax = height[l-i-1]
}
p[i] = pMax
s[l-i-1] = sMax
}
res := 0
for i := 0; i < l; i++ {
res += min(p[i], s[i]) - height[i]
}
return res
}
func min(a, b int ) int {
if a < b {
return a
}
return b
}
时空复杂度分析: 一次循环求解前后缀值, 一次循环求解结果, 开辟两个长度为n的数组记录前后缀
- 时间复杂度O(n)
- 空间复杂度O(n)
解法二
①简化空间复杂度:
由于我们在遍历的过程中, 所求的为前后缀的最大值, 所以可以考虑用变量的形式直接记录前后缀的最大值. 这样的话i处的青豆数量 res[i]=min(prefix, suffix])-height[i]
在代码中体现为用p和s变量维护最大前后缀值.
②最关键的一步:
在我们确定了i处前后缀谁大这个问题时, 例如前缀最大值较大. 那么此时i-1处的前后缀最大值的较小值已经确定了, 即用前缀最大值与i处的高度比较, 较小者即为结果. 所以可以利用双指针优化:
func trap(height []int) int {
//双指针: 前后缀的较小值就是i处可接的青豆
l := len(height)
bef, aft, res := 0, l-1, 0
p, s := 0, 0
for bef < aft {
if height[bef] > p {
p = height[bef]
}
if height[aft] > s {
s = height[aft]
}
if p > s {
res += s - height[aft]
aft--
}else {
res += p - height[bef]
bef++
}
}
return res
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
时空复杂度分析: 由于前后缀的计算是由双指针向中间推进, 所以最终计算次数为n, 开辟了若干int型变量, 为常数空间复杂度
- 时间复杂度O(n)
- 空间复杂度O(1)
结果测试
package main
import "fmt"
func main() {
height1 := []int{0,1,0,2,1,0,1,3,2,1,2,1}
height2 := []int{4,2,0,3,2,5}
height3 := []int{5,0,2,1,4,0,1,0,3}
fmt.Println(trap(height1))
fmt.Println(trap(height2))
fmt.Println(trap(height3))
}
//结果为: 6 9 17
总结
一开始考虑的解法是: 寻找峰值, 然后在谷底处必然可以攒青豆, 但是有个问题就是较低的峰值在高的峰值之间, 也会退化成谷地, 所以不可行.
以上的两种解法拆分了问题, 不存在局部最优和全局最优的矛盾性.
最后, 由于我初学Golang, 语法不够熟悉, 希望大家对我的不规范予以指出, 感激不尽.
另: Golang中有快捷实现func max/min(a, b int) int的方法吗??