当青训营遇上码上掘金
嗯,开头加好了,这次的码上掘金活动,后端部分是两道算法题,一道广搜一道双指针。
主题 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 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
这里就直接使用 Leetcode 上的截图了。
不知道各位有没有看过那种多个木板组成的水桶,如果直立着正放的话,能装多少水只取决于最短的木板的顶端到水桶底部的距离,这题也类似,某格能装的水取决于较低的岸边到底部的距离。故可以使用双指针维护左岸和右岸。
维护两个指针 left 和 right,以及两个变量 leftMax 和 rightMax,初始时指针分别指向。指针 left 向右移动,指针 right 向左移动,在移动指针的过程中维护两个变量 leftEdge 和 rightEdge 的值。
当两个指针没有相遇时,进行如下操作:
1、让目前岸比较低的一边判断下自己和该格底部的距离
2、如果高于上次的岸边,则更新岸的高度
3、如果低于,则记录水量
4、移动指针指向下一格
(注意下这样写的话,就需要在判断循环的条件里加上等于,不然会少一格)
当两个指针相遇时,结束循环,统计的结果即为能接的雨水总量。
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
}