接青豆|字节青训营|码上掘金

161 阅读2分钟

当青训营遇上码上掘金

本篇文章归档于 “第五届字节跳动青训营”,主要是为了完成和记录掘金的 “青训营 x 码上掘金” 活动,如果你对我的其他文章感兴趣,可以去我的 专栏 中逛逛看有没有你想要的东西。

后端营推荐了两个可供选择的题目:寻友之旅和接青豆。

“寻友之旅” 是一道非常经典的动态规划,感觉并没有太多可扩展的空间,于是我打算简单说说 “接青豆” 的解题思路。

什么是 “接青豆”

先附上活动给的题目描述:

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

再来张官方的示意图,或许会更清晰些: (水篇幅嫌疑,嘿嘿!;)

接青豆的示意图

有经常刷题的同学可能已经反应过来了,这其实是一道 hard 的 “接雨水”,如果你想立刻跑去 coding,这里顺手给个通道 接雨水(习惯英文版的看这里 Trapping rain water

如何 “接青豆”

简单来说,无论是 “接雨水”,还是 “接青豆”,实际上就是水桶效应,桶内的容量与木板的长度有关,并由最短的木板来定。

方法一:比较最长板

我们可以维护两个数组:toHead, toTail。记录每个状态下的最高板长,区别在于 toHead 是从头向尾比较,toTail 反之。

    // n is length of array
    n := len(height)
    // toHead store the heightest pillar from current to the head of array, toTail is opposite
    toHead, toTail := make([]int, n), make([]int, n)
    for max, i := 0, 0; i < n; i++ {
      if max < height[i] {
        max = height[i]
      }
      toHead[i] = max
    }
    for max, i := 0, n - 1; i >= 0; i-- {
      if max < height[i] {
        max = height[i]
      }
      toTail[i] = max
    }

通过遍历两次数组,我们可以拿到每个状态下,相较于两端的最长板。此时,每个状态能承载的容量就是较小的那个“最长板”。最后,再遍历一次,统计每个状态下的容量即可。

    // count beans
    for i := 0; i < n; i++ {
      // lower one between toHead and toTail is top of the beens
      top := min(toHead[i], toTail[i])
      result += top - height[i]
    }

方案二:比较边缘板

方案一的思路很符合人类的思考模式,也非常容易理解。

美中不足在于它需要遍历三遍数组,并且需要维护两个记录状态的数组。当原始数据非常大时,它需要再开辟两个同样大小的空间,并便利 3 次。

因此,我们可以尝试寻找一个更高效的解决方案。先来看这样一个场景:假设桶内有多个隔板做隔断,那桶的实际容量应该有多少?

  • 如果隔板高于边缘板:那边缘板就是最短板,从边缘到隔板的容量都是边缘板
  • 如果隔板低于边缘板:那较短的边缘板就是最短板,隔板的高度仅在于减少了容量,容量依旧是较短的边缘板。

现在,你是否已经想到了更好的解决方案了呢?

这是一条 break line:可以短暂停留,希望你能简单思考一下~

我们需要两个值:max_left,max_right。max_left 是左边缘板的长度,max_right 同理。为什么前缀加了一个 max?因为我们需要维护边缘板的长度。

然后,让两端的边缘板不断逼近,移动较短边缘板的与此同时记录每个状态的容量即可,直到相遇:

    // max_left is the biggest value from left, max_right as well
    max_left, max_right := 0, 0
    // head & tail are the index of array
    head, tail := 0, len(height) - 1
    // iterate over the array
    for head <= tail {
      if max_left <= max_right {
        if max_left < height[head] {
          max_left = height[head]
        } else {
          result += max_left - height[head]
        }
        head++
      } else {
        if max_right < height[tail] {
          max_right = height[tail]
        } else {
          result += max_right - height[tail]
        }
        tail--
      }
    }

最后放一点想说的话

文章是在初一写的,先祝看到这里的读者:新年快乐~

方案二相较于方案一可能会难理解一些,不过还是值得仔细回味,如果有什么问题,欢迎评论区留言。