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

95 阅读2分钟

当青训营遇上码上掘金......呃呃,好了正文开始.

题目&例

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

  • in: height = [5,0,2,1,4,0,1,0,3]
  • out: 17 image.png

整体思路

每一列的青豆数取决于它的 左右最高一列的较低一列高度(顶)本身的高度(底) ,公式为 结果=结果=顶-底. 得到了每一列的青豆数,只要求sum即可得到最终的结果.

以下两种方法都是这个思路,只是在"各自求出左右最高一列的高度"时使用了不同思路.

题解1: 中心扩散

func findHighestOne(dir, col int) int

Find the hightest one lefter/righter than the column col. "dir" control the direction, -1 is left,1 is right. The function return the height.

note: the return number >= height[col] (i.e. if nothing is bigger than height[col], the function will return itself).

func getColBeanNum(col, l, r int) int

The function return the capacity of the column by formular topbottumtop-bottum. The top is the lower column between the left-highest and the right-highest, while the bottum is height[col] itself.

note: if left-highest and the right-highest both are lower than height[col] itself, the top also is height[col] itself, due to the feature of findHighestOne().

package main

import (
	"fmt"
)

var height []int

func main() {
	height = []int{5, 0, 2, 1, 4, 0, 1, 0, 3}
	allBeanNum := 0
	for i := 0; i < len(height); i++ {
		l := findHighestOne(-1, i)
		r := findHighestOne(1, i)
		colBeanNum := getColBeanNum(i, l, r)
		allBeanNum += colBeanNum
	}
	fmt.Println(allBeanNum)
}

// find the hightest one lefter/righter than the column col.
// dir control the direction, -1 is left,1 is right.
// return the height.
// note: the return number >= heigt[col],
// i.e. if nothing is bigger than heigt[col], the function will return itself.
func findHighestOne(dir, col int) int {
	highestHeight := 0
	for idx := col; idx >= 0 && idx < len(height); idx += dir {
		if height[idx] > highestHeight {
			highestHeight = height[idx]
		}
	}
	return highestHeight
}

// return the capacity of the column by formular `top-bottum`.
// the top is the lower column between the left-highest and the right-highest,
// the bottum is height[col] itself.
// note: if left-highest and the right-highest both are lower than height[col] itself,
// the top also is height[col] itself.
func getColBeanNum(col, l, r int) int {
	top := l
	if r < l {
		top = r
	}
	btm := height[col]
	return top - btm
}

题解2: 动态规划

func findHighestOne(dir, col int) int

Find the hightest one lefter/righter than the column col. "dir" control the direction, -1 is left,1 is right. The function return the height.

note: different from method1 the return number may be lower than height[col].

func getColBeanNum(col, l, r int) int

The function return the capacity of the column by formular topbottumtop-bottum. The top is the lower column between the left-highest and the right-highest, while the bottum is height[col] itself.

note: if the lower one between the left-highest and the right-highest is lower than height[col] itself, the result will be 0.

package main

import (
	"fmt"
)

var height []int
var lmax, rmax []int

func main() {
	height = []int{5, 0, 2, 1, 4, 0, 1, 0, 3}
	lmax, rmax = make([]int, len(height)), make([]int, len(height))
	allBeanNum := 0
	for i := 1; i < len(height)-1; i++ {
		findHighestOne(-1, i)
	}
	for i := len(height) - 2; i > 0; i-- {
		findHighestOne(1, i)
	}
	for i := 1; i < len(height)-1; i++ {
		colBeanNum := getColBeanNum(i, lmax[i], rmax[i])
		allBeanNum += colBeanNum
	}
	fmt.Println(allBeanNum)
}

// find the hightest one lefter/righter than the column col.
// dir control the direction, -1 is left,1 is right.
// return the height.
func findHighestOne(dir, col int) (result int) {
	switch dir {
	case -1:
		lmax[col] = max(lmax[col-1], height[col-1])
		result = lmax[col]
	case 1:
		rmax[col] = max(rmax[col+1], height[col+1])
		result = rmax[col]
	}
	return
}

// return the capacity of the column by formular `top-bottum`.
// the top is the lower column between the left-highest and the right-highest,
// the bottum is height[col] itself.
func getColBeanNum(col, l, r int) (result int) {
	top := l
	if r < l {
		top = r
	}
	btm := height[col]
	switch {
	case top > btm:
		result = top - btm
	case top == btm:
		result = 0
	case top < btm:
		result = 0
	}
	return
}

// return the bigger one
func max(a, b int) int {
	if a < b {
		return b
	}
	return a
}

总结

最后water一些大家都知道的东西.

  • 方法1的时间复杂度是O(n2)O(n^2);如果不算原始数组的话,空间复杂度是O(1)O(1).
  • 方法2的时间复杂度是O(n)O(n);如果不算原始数组的话,空间复杂度是O(n)O(n).

所以各有取舍......,然我在方法二时预设了第一列和最后一列是0青豆,所以遍历的时候跳过了这俩,对于有点强迫症的我来说方法一更优雅(然并卵).