[青训营]用水平激光消除青豆来处理青豆问题

449 阅读3分钟

当青训营遇上码上掘金

青训营主题4

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

image.png

解决代码示例code.juejin.cn/api/raw/718…

这个问题,如果我们想象成青豆向上往下坠落,而多出来的豌豆会滑出这个空间,从而计算体积的话,这个动态过程是很难模拟的...我通过观察发现,如果我们从左边和右边去观察,你会发现你是看不见青豆的,这让我萌发出来一个想法,若青豆是填满了柱子所在的空间,然后通过消除法不断的消除青豆,最后剩下来的就是能够接住的青豆,这样的思考方式是静态的,而不是动态的...

接下来 我用我画的一个示例图来展示我的想法

image.png

青豆只会从左边和右边滑落

若是右边的豆子的滑落,则从右向左数第一个最高柱子是从右滑落的左边界

image.png

并且,当我们从右向左检索看见最高柱子时,这个柱子后面的青豆绝不可能从右滑落了

按照这个逻辑,我们可以最开始就把这个空间想象成柱子数为长,柱子最高长度为高的长方体,填满了青豆,这个时候,我们轻而易举可以算出豌豆数量count(长*宽-柱子高度和)

image.png

想象左边和右边摆放着一台水平激光照射装置,凡是照到了的豌豆全部消除(凡是消除,就让总的count数-此部分数量)

接下来 我使用go语言来实现此功能

var n int
fmt.Scanf("%d\n", &n)
var height []int
var k int
max := 0
sum := 0

首先定义初始的值

n为柱子数量(包括高度为0的柱子数)

height是柱子高度统计切片

k是中间量

max是最高柱子高度

sum是柱子高度合计

for i := n; i > 0; i-- {
   fmt.Scanf("%d", &k)
   if k > max {
      max = k
   }
   sum += k
   height = append(height, k)
}

通过此循环来输入柱子高度值,并统计出max的值

count := n*max - sum

count值是目前所有的青豆数

image.png 先让右边的激光向左照射,注意,为什么是count-=(...)*(n-1-l)

因为l与r的作用是通过此下标查到其柱子高度,算出清除高度差作为长方形消除的高,而长永远是左边界到最右边(下标为n-1)的长度,以右边消除是(n-1-l)

l := n - 1
r := n - 1
for {
   l--
   if l < 0 {
      break
   }
   if height[l] > height[r] {
      count -= (height[l] - height[r]) * (n - 1 - l) //总数中减去那块长方形青豆
      r = l                                          //让l成为起点
   }
   if height[r] == max { //若初始边界已经是最大了,则这个柱子已经抵挡所有激光,后面都会被保护
      break
   }
}

l和r都是下标(l<r),l一直往左边走,r不动,只要出现了height[l]>height[r]的情况,就开始清除,并让r=l,继续清除,只要l<0或者r=max的时候,就没有继续往下走的必要了(因为豆子不可能再往这个方向落了)

模拟一下上面代码的清除过程

image.png

image.png

左边也是一样,注意,为什么count-=(...)*r

下标0与r之间一定是清除长方体的长,高度是height[r]-height[l]

l = 0
r = 0
for {
   r++
   if r > n-1 {
      break
   }
   if height[r] > height[l] {
      count -= (height[r] - height[l]) * r
      l = r
   }
   if height[l] == max {
      break
   }
}

模拟一下清除的过程

image.png 此图n=12 {3,1,0,5,0,2,5,0,4,0,0,2} 青豆应剩21

函数输出为21

证实是没有问题的