「青训营 X 码上掘金」| 攒青豆(Go实现)

52 阅读3分钟

当青训营遇上码上掘金

题目:攒青豆

现有 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)

这里采用的是 单调栈 方法

总体的原则就是:

  1. 当前高度小于等于栈顶高度,入栈,指针后移。

  2. 当前高度大于栈顶高度,出栈,计算出当前墙和栈顶的墙之间水的多少,然后计算当前的高度和新栈的高度的关系,然后接着重复第 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 时,如果栈内至少有两个元素,记栈顶元素为 toptop 的下面一个元素是 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 入栈,继续遍历后面的下标,计算凹槽中的雨水量。遍历结束之后即可得到能接的雨水总量。