「青训营 X 码上掘金」主题创作活动主题4:攒青豆

54 阅读2分钟

当青训营遇上码上掘金

主题4:攒青豆

题目

现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)

image.png

思路

  • 这个题和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}))
}