当青训营遇上码上掘金之“攒青豆”

105 阅读3分钟

当青训营遇上码上掘金会发生什么奇妙的事情呢,那就不得不提到这个可爱又经典的题目啦。

题目描述

image.png

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

不考虑边角堆积,柱子和青豆在二维切面中的每格视为 1 单位。

示例:

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

分析

首先分析能够存青豆的前提条件,显然需要当前位置的左右两边均要有高于当前位置高度的柱子,且青豆的数量取决于最矮的一端。

因此最先应该可以想到使用双指针的方法,从整个 n 个柱子的左右两端开始逐渐向中间移动,计算移动过程中的青豆数。同时使用两个变量 maxLeftHeightmaxRightHeight 分别记录移动期间左右两端柱子的最大高度,随着指针的移动不断进行更新。

接下来就需要思考如何计算每个位置的青豆数。

刚才提到,每个位置的青豆数取决于当前位置左右两端最矮一端的柱子高度,而高柱子并没有起到对于青豆数计算的决定性作用,因此每次都将矮柱子一端的指针向中间移动,计算每个位置的青豆数量,直至二指针相遇。

最后就是每个位置青豆数的计算的问题。

这个就显而易见了,只需要计算矮柱子和当前位置的高度差就可以啦。

代码

关键代码及注释

func trapBean(heights []int) (ans int) {
    // 高度数组不符合计算要求,直接返回
    if heights == nil || len(heights) <= 2 {
        return 0
    }
    
    // 左右两端指针
    left, right := 0, len(heights)-1
    // 左右两端移动过程中最大高度
    maxLeftHeight, maxRightHeight := heights[left], heights[right]
    
    // 两指针相遇时结束
    for left < right {
        // 获取最大高度
        maxLeftHeight = max(maxLeftHeight, heights[left])
        maxRightHeight = max(maxRightHeight, heights[right])
        
        // 判断哪端矮,就用哪端的最大高度减去当前高度得到当前位置的青豆数
        if heights[left] <= heights[right] {
            ans += maxLeftHeight - heights[left]
            left++
        } else {
            ans += maxRightHeight - heights[right]
            right--
        }
    }
    return
}

代码片段

复杂度分析

  • 时间复杂度: 每次移动只进行减法计算,且两指针总共移动次数不会超过高度数组长度 n,所以时间复杂度为 O(n)
  • 空间复杂度: 只使用了变量进行存储,所以空间复杂度为 O(1)

鸣谢