当青训营遇上码上掘金入营版主题3、4 思路及代码解析(Go语言实现)

292 阅读4分钟

当青训营遇上码上掘金

嗯,开头加好了,这次的码上掘金活动,后端部分是两道算法题,一道广搜一道双指针。

主题 3:寻友之旅

首先来看下这次的题目。

小青要找小码去玩,他们的家在一条直线上,当前小青在地点 N ,小码在地点 K (0≤N , K≤100 000),并且小码在自己家原地不动等待小青。小青有两种交通方式可选:步行和公交。 步行:小青可以在一分钟内从任意节点 X 移动到节点 X-1 或 X+1 公交:小青可以在一分钟内从任意节点 X 移动到节点 2×X (公交不可以向后走)

请帮助小青通知小码,小青最快到达时间是多久? 输入: 两个整数 N 和 K 输出: 小青到小码家所需的最短时间(以分钟为单位)

这是算是一道广度优先搜索的经典题目了。

首先解释下什么叫广度优先搜索(Breadth First Search 简称 BFS)。广度优先搜索是指遍历一张图的时候,优先找和起始点 s 距离为 k 的节点,再去找和起始节点 s 距离大于 k 的节点,即广度优先。

对于从根节点开始遍历的一棵树而言,就是先找完一层的节点,再找下一层。

我们把小青做出某步决策后所处的位置看成一个个节点,把小青的起始地点 N 看做根节点,则小青的所有方案可以构成一棵三叉树(这里注意下公交车不能向后走,所以只有 -1、+1、*2 三种走法)。

这道题可以直接逆向来求,让小码去找小青会省一点判断条件,如果题目有右边界要求的话也不用判断是否坐公交坐出右边界(坐过站了 XD)。因为逆向来求的话,如果小码在途中正好处于奇数位置的时候,小青是不能通过做公交来到达该点的,只能步行。

这样走法就变成了 -1、+1、/2 三种。

遍历这棵三叉树,每次到叶子节点判断下是否需要更新最小值即可。

(说是三叉树,实际代码不用建树,用递归就能写,注意进入下一层函数前步数+1,回来后步数-1即可)

类似题目:P1588 [USACO07OPEN]Catch That Cow S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

AC代码:

 var x, y int
 var nowStep = 0
 var minStep = math.MaxInt // 不支持的话,直接写成就好9223372036854775807
 ​
 func bfs(location int) {
     if x == location {
         if nowStep < minStep {
             minStep = nowStep
         }
         return
     }
 ​
     if location/2 == x {
         if nowStep+1 < minStep {
             minStep = nowStep + 1
         }
         return
     }
 ​
     // 不能向回走
     if location < x {
         if nowStep+x-location < minStep {
             minStep = nowStep + x - location
         }
         return
     }
 ​
     if location%2 == 0 {
         // 跳了一步还是大于 N,直接跳即可
         if location/2 >= x || x-location/2 < location-x {
             nowStep++
             bfs(location / 2)
             nowStep--
         } else {
             if nowStep+location-x < minStep {
                 minStep = nowStep + location - x
             }
         }
     } else {
         // 先向前
         nowStep++
         bfs(location + 1)
         nowStep--
         // 再向后
         nowStep++
         bfs(location - 1)
         nowStep--
     }
 ​
 }
 ​
 func main() {
     var t int
     // 注意读取换行
     fmt.Scanf("%d\n", &t)
 ​
     for t > 0 {
         t--
         fmt.Scanf("%d%d\n", &x, &y)
         bfs(y)
         fmt.Println(minStep)
         minStep = math.MaxInt
     }
 }

主题 4:攒青豆

这道题是传说中字节面试里考的最简单的算法:接雨水。

还是先来看下题目。

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

image-20230204225626690.png

这里就直接使用 Leetcode 上的截图了。

不知道各位有没有看过那种多个木板组成的水桶,如果直立着正放的话,能装多少水只取决于最短的木板的顶端到水桶底部的距离,这题也类似,某格能装的水取决于较低的岸边到底部的距离。故可以使用双指针维护左岸和右岸。

维护两个指针 left 和 right,以及两个变量 leftMax 和 rightMax,初始时指针分别指向。指针 left 向右移动,指针 right 向左移动,在移动指针的过程中维护两个变量 leftEdge 和 rightEdge 的值。

当两个指针没有相遇时,进行如下操作:

1、让目前岸比较低的一边判断下自己和该格底部的距离

2、如果高于上次的岸边,则更新岸的高度

3、如果低于,则记录水量

4、移动指针指向下一格

(注意下这样写的话,就需要在判断循环的条件里加上等于,不然会少一格)

当两个指针相遇时,结束循环,统计的结果即为能接的雨水总量。

类似题目:42. 接雨水 - 力扣(Leetcode)

AC 代码:

 func trap(height []int) int {
     if len(height) <= 1 {
         return 0
     }
 ​
     res := 0
     left, right := 0, len(height)-1
     leftEdge, rightEdge := height[left], height[right]
     // 两个指针相遇则为结束,因为更新了下标之后会再多向右/左走一步,所以等于的情况也要进一次循环
     for left <= right {
         // 低的那一边移动
         if leftEdge < rightEdge {
             if leftEdge < height[left] {
                 leftEdge = height[left]
             } else {
                 res += leftEdge - height[left]
             }
             left++
         } else {
             if rightEdge < height[right] {
                 rightEdge = height[right]
             } else {
                 res += rightEdge - height[right]
             }
             right--
         }
     }
 ​
     return res
 }