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

75 阅读3分钟

题目背景

当青训营遇上码上掘金

  • 主题 4:攒青豆

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

攒青豆.png

以下为上图例子的解析:

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

思路分析

这道题目是一道典型的模拟题,和经典题目接雨水是一样的,其主要思路是模拟雨水从高处流到低处的过程,然后计算每一列能够容纳的雨水量,最后将所有列的雨水量相加得到总体积。

方法一是最直接的暴力解法,先求出每一列左边和右边的最高高度,然后对于每一列,计算其容纳的雨水量,然后将所有列的雨水量相加即可得到总体积。这种方法比较直观,但是需要用两个数组分别存储左边和右边的最高高度,空间复杂度较高。

方法二是用单调栈的方法解决这个问题。对于每一列,如果其高度小于栈顶元素,那么将其压入栈中,否则就弹出栈顶元素,并计算出其容纳的雨水量。容纳的雨水量可以通过计算当前列和栈顶元素之间的距离,以及它们之间的高度差来计算。这种方法比较巧妙,只需要一个栈就可以完成计算,空间复杂度较低。

需要注意的是,在方法二中,栈中存储的是每一列的下标,而不是高度。这是因为对于每一列,我们需要知道其左边和右边最高的列的高度,因此直接存储高度是不够的,需要存储下标。

这道题目可以帮助我们更好地理解单调栈的使用方法,以及如何通过单调栈来解决一些类似的问题。同时,这道题目也需要我们仔细思考每一个细节,例如如何计算容纳的雨水量,以及如何存储每一列的左边和右边最高的列的高度,这些都是需要仔细思考的。

总之,这道题目对于我们的编程能力和思维能力都是一个不错的挑战。通过不断地练习和思考,我们可以更好地掌握单调栈的使用方法,提高我们的编程能力和思维能力,同时也能够更好地应对类似的编程问题。

代码实现

package main

import "fmt"

func main() {
	fmt.Println(method02([]int{5, 0, 2, 1, 4, 0, 1, 0, 3}))
}

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

func method01(height []int) int {
	l := len(height)
	if l == 0 {
		return 0
	}
	maxL := make([]int, l)
	maxR := make([]int, l)
	maxL[0] = 0
	maxR[l-1] = 0
	for i := 1; i < l; i++ {
		if height[i-1] > maxL[i-1] {
			maxL[i] = height[i-1]
		} else {
			maxL[i] = maxL[i-1]
		}
		if height[l-i] > maxR[l-i] {
			maxR[l-1-i] = height[l-i]
		} else {
			maxR[l-1-i] = maxR[l-i]
		}
	}
	sum := 0
	var rain int
	for i := 0; i < l; i++ {
		rain = min(maxL[i], maxR[i]) - height[i]
		if rain > 0 {
			sum += rain
		}
	}
	return sum
}

func method02(height []int) int {
	var s []int
	var ret int
	for i, h := range height {
		for len(s) > 0 && height[s[len(s)-1]] < h {
			top := s[len(s)-1]
			s = s[:len(s)-1]
			if len(s) == 0 {
				break
			}
			t := s[len(s)-1]
			W := i - t - 1
			H := min(height[t], h) - height[top]
			ret += W * H
		}
		s = append(s, i)
	}
	return ret
}