码上掘金-攒青豆 | 青训营笔记

44 阅读2分钟

当青训营遇上码上掘金

  • 题目:攒青豆

    现有 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 个单位的青豆。

解析

  • 要点 1: 对于某一个位置i, 我们能否接这个位置上的青豆, 取决于i左右两侧的最大值(leftMax, rightMax)是否比height[i]大. 只有当i左右两侧的最大值都比height[i]大时, 才能接i上的豆, 数量为min(leftMax, rightMax) - height[i]; 这里得出结论, 限制当前位置接豆的条件是其左右两侧最大值中的较小值min(leftMax, rightMax).
  • 要点 2: 对于ij两个指针, i从左向右移动, j从右向左移动, 对于指针i来说, leftMax是真实可信的, 因为leftMax的值是i一步一个脚印走出来的, 但是rightMax是不真实不可信的, 因为i不知道从height[i]height[j]之间是否有其他的数大于rightMax; 同样,对于j来说rightMax是真实可信的, leftMax值是不真实不可信的. 这里得出结论: 对于左指针i, 它右侧的真实的最大值 >= rightMax, 对于右指针j, 它左侧的真实的最大值 >= leftMax.
  • 要点 3: 基于要点 1, 我们知道对于一个位置来说影响它接豆的应该是左右两侧最大值中的较小值, 也就是min(leftMax, rightMax), 而基于要点 2, 我们知道左指针右侧的真实最大值会大于等于rightMax, 所以, 当出现leftMax < rightMax的时候, 左指针的位置是否能接豆就已经确定了, 同样, 当出现leftMax >= rightMax的时候, 右指针的位置是否能接豆就已经确定了, 所以, 可以写出下面的代码了.
    • 另外关于代码中的循环条件while (i < j), 今天我再看时有了些疑问, 为什么不是while (i <= j)也能通过. 我又仔细想了下, 并做了些试验, 结果发现最后当i == j的时候, 它们指向的一定是数组中最大的那个数, 这个最大的数是不能接到雨水的.

实现


func trap_bean(height []int) (ans int){
  left, right := 0, len(height) - 1
  leftMax, rightMax := 0, 0
  for left < right {
    leftMax = max(leftMax, height[left])
    rightMax = max(rightMax, height[right])

    if height[left] < height[right] {
      ans += leftMax - height[left]
      left++
    } else {
      ans += rightMax - height[right]
      right--
    }
  }
  return
}

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