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

58 阅读2分钟

当青训营遇上码上掘金

我在此活动中选择的是主题四:攒青豆,下面是我对此主题的理解

主题介绍

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

image.png

以下为上图例子的解析:

输入:height = [5,0,2,1,4,0,1,0,3] 
输出:17 
解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。

思路解析

看见了这个活动就想到了接雨水的问题,其实两个问题差不多,方法也一样。方法如下

方法1(按列算)

  1. 这也是最容易理解的一种方法,我们计算每一个柱子上方的青豆最多有多高就行了,而这个高度取决于它的左右两边最高的柱子分别是多高。
  2. 也可以求左右两端最高的高度,不过需要预处理一下,用数组保存一下每个位置左右两端最高的高度就行了。
  3. 最后就是用左右两边最高高度的较小值,减去这根柱子的高度。
  4. 时间复杂度 ,空间复杂度 ,需要扫描两遍数组。

方法2 双指针法

左右指针向中间移动。左指针是左边柱子最大高度,右指针是右边柱子最大高度。当左指针小于右指针时,左指针右移;当左指针大于等于右指针时,右指针左移。

时间复杂度:O(N)。

空间复杂度:O(1)。

方法思路:

  1. 先定义左右两个柱子和总青豆数,能攒多少青豆就看左右两个柱子高度
  2. 再定义第二个和倒数第二个的两个柱子,让柱子分情况向中间遍历
  3. 如果左边柱子小于等于右边柱子,青豆数增加,根据左边柱子和第二个柱子比较最大值,更新为左边的柱子
  4. 如果左边柱子大于右边柱子,青豆数增加,根据右边柱子和倒数第二个柱子比较最大值,更新为右边的柱子
  5. 当左右两个指针为同一位置时,结束循环。得最终青豆数

代码实现

代码实现采用方式二双指针形式,代码如下:

package main

import "fmt"

func main() {
	height := []int{5, 0, 2, 1, 4, 0, 1, 0, 3}
	ret := trap(height)
	fmt.Println(ret)
}

func trap(height []int) int {
	N := len(height)
	if N <= 2 {
		return 0
	}
	left := height[0]
	right := height[N-1]
	L := 1
	R := N - 2
	count := 0
	for L <= R {
		if left < right {
			count += getMax(getMin(left, right)-height[L], 0)
			left = getMax(left, height[L])
			L++
		} else {
			count += getMax(getMin(left, right)-height[R], 0)
			right = getMax(right, height[R])
			R--
		}
	}
	return count
}

func getMax(a int, b int) int {
	if a > b {
		return a
	} else {
		return b
	}
}

func getMin(a int, b int) int {
	if a < b {
		return a
	} else {
		return b
	}
}