当青训营遇上码上掘金
题目:攒青豆
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
以下为上图例子的解析:
输入:height = [5,0,2,1,4,0,1,0,3]
输出:17
解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。
分析思路
经常水 leetcode 的同学已经反应过来了,这就是那个“臭名昭著”的 接雨水 翻版好吧(龙王歪嘴.jpg)
| 方法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 暴力 | O(N^2) | O(1) |
| 动态规划 | O(N) | O(N) |
| 双指针 | O(N) | O(1) |
| 单调栈 | O(N) | O(N) |
这里采用的是 单调栈 方法
总体的原则就是:
-
当前高度小于等于栈顶高度,入栈,指针后移。
-
当前高度大于栈顶高度,出栈,计算出当前墙和栈顶的墙之间水的多少,然后计算当前的高度和新栈的高度的关系,然后接着重复第 2 步。直到当前墙的高度不大于栈顶高度或者栈空,然后把当前墙入栈,指针后移。
需注意的是:单调栈就是比普通的栈多一个性质,即维护栈内元素单调。
比如当前某个单调递减的栈的元素从栈底到栈顶分别是:[10, 9, 8, 3, 2],如果要入栈元素5,需要把栈顶元素pop出去,直到满足单调递减为止,即先变成[10, 9, 8],再入栈5,就是[10, 9, 8, 5]。
代码实现:
func trap(height []int) int {
// 设置单调栈
singleStack:=[]int{}
res:=0
// 遍历数组
for i,v:=range height {
// 如果当前元素大于栈顶元素
for len(singleStack)>0 && v> height[singleStack[len(singleStack)-1]]{
top:=singleStack[len(singleStack)-1] //存入栈作为栈顶元素
singleStack=singleStack[:len(singleStack)-1]//出栈
// 如果栈为空,说明栈顶元素的左边没有邻近高柱,构不成凹槽
if len(singleStack)==0{
break
}
// 构成凹槽
left:=singleStack[len(singleStack)-1]//这里是原栈顶下面的一个元素
curWidth:=i-left-1
curHeight:=min(height[left],v)-height[top]
res+=curWidth*curHeight
}
// 入栈
singleStack=append(singleStack,i)
}
return res
}
func min(x,y int) int{
if x>y{
return y
}
return x
}
代码解析
-
维护一个单调栈,单调栈存储的是下标,满足从栈底到栈顶的下标对应的数组
height中的元素递减。 -
从左到右遍历数组,遍历到下标
i时,如果栈内至少有两个元素,记栈顶元素为top,top的下面一个元素是left,则一定有height[left]≥height[top](因为只有高度 <= 栈顶元素 才入栈)。如果height[i]>height[top],则得到一个可以接雨水的区域,该区域的宽度是i−left−1,高度是min(height[left],height[i])−height[top],根据宽度和高度即可计算得到该区域能接的雨水量。 -
为了得到
left,需要将top出栈。在对top计算能接的雨水量之后,left变成新的top,重复上述操作,直到栈变为空,或者栈顶下标对应的height中的元素大于或等于height[i]。 -
在对下标
i处计算能接的雨水量之后,将i入栈,继续遍历后面的下标,计算凹槽中的雨水量。遍历结束之后即可得到能接的雨水总量。