当青训营遇上码上掘金
Description
寻友之旅
小青要找小码去玩,他们的家在一条直线上,当前小青在地点 N ,小码在地点 K (0≤N , K≤100 000),并且小码在自己家原地不动等待小青。小青有两种交通方式可选:步行和公交。
步行:小青可以在一分钟内从任意节点 X 移动到节点 X-1 或 X+1
公交:小青可以在一分钟内从任意节点 X 移动到节点 2×X (公交不可以向后走)
请帮助小青通知小码,小青最快到达时间是多久?
输入: 两个整数 N 和 K
输出: 小青到小码家所需的最短时间(以分钟为单位)
题目中没有给测试用例,这里我们可以自己思考出两个测试用例:
Example 1:
- input:5 17
- output:4
- description:5-1=4 -> 4 x 2 = 8 -> 8 x 2 = 16 -> 16+1=17
Example 2:
- input:5 19
- output:3
- description:5 x 2 = 10 -> 10 x 2 = 20 -> 20-1=19
Thinking
此题为一道寻找最少路径的题,可以把小青的地点 N1 看作根节点,展开出三个子节点:N2(N1-1)、N3(N1+1)、N4(N1x2),这是第二层;随后N2、N3、N4又可以分别展开3个子节点,从而得到一个多叉树。直到得到一个值为 K 的子节点,这显然是一个树的遍历问题,而需要得到的时间就是树的层级
说到搜索问题,就可以想到两种常见的树的遍历方式:
- DFS(深度优先搜索)
- BFS(广度优先搜索)
我们来回顾一下这两种方式
深度优先搜索(DFS)
- 沿着树的深度遍历树的节点,尽可能深的搜索树的分支
- 当节点 v 的所在边都己被探寻过或者在搜寻时结点不满足条件,搜索将回溯到发现节点 v 的那条边的起始节点
- 整个进程反复进行直到所有节点都被访问为止
广度优先搜索(BFS)
- 一层一层的遍历,像水的波纹一样扩散开来,每次都尝试访问同一层的节点。如果同一层都访问完了,再访问下一层
区别:
- 顾名思义,深度优先搜索沿着树的深度遍历树的节点,广度优先搜索沿着树的层级遍历树的节点
- 深度优先搜索本质是递归,是回溯,适用于寻找两个节点之间的路径等
- 广度优先搜索通常用于解决求最短路径问题,当第一次达到目的节点时一定是最短路径
这里需要得到的时间就是树的层级,又是一道寻找最少路径的题,显然我们要用广度优先搜索(BFS)
Related Topics
知道了要用广度优先搜索(BFS),我们再来想想如何实现代码
首先广度优先搜索(BFS)用到的数据结构是队列,将同层节点放在一个队列里,遍历同层节点,每层的遍历次数与该层节点数目相同,将同层的每个节点展开的三个节点放在队列后面等待下一层遍历
我们直接上模板(这里的模板是遍历二叉树的模板):
//BFS模板
func bfs(p *TreeNode) []int {
res := make([]int, 0)
if p == nil {
return res
}
queue := []*TreeNode{p} //将同层节点放在一个队列里
for len(queue) > 0 {
length := len(queue) //每层的遍历次数与该层节点数目相同
for length > 0 {
length--
if queue[0].Left != nil {
queue = append(queue, queue[0].Left)
}
if queue[0].Right != nil {
queue = append(queue, queue[0].Right)
}
res = append(res, queue[0].Val)
queue = queue[1:] //及时舍弃
}
}
return res
}
通过模板可以总结到:
- Go 中用数组实现队列,轻松快捷
- 注意及时舍弃遍历过的节点
- 我们的题目中不是一个树的数据结构,直接用整型数组作为队列即可
Solution
package main
import "fmt"
func main() {
var n, k, time int
fmt.Println("Please input n,k:")
fmt.Scanf("%d %d", &n, &k)
queue := []int{n} //根节点放进来
for len(queue) > 0 { //遍历每层
time++ //表示层级加了1
length := len(queue)
for length > 0 { //每层的遍历次数与该层节点数目相同
length--
if queue[0]+1 == k || queue[0]-1 == k || queue[0]*2 == k { // 遍历到结果直接输出再 return
fmt.Println(time)
return
}
queue = append(queue, []int{queue[0] + 1, queue[0] - 1, queue[0] * 2}...) //Go语法糖”...“
queue = queue[1:] //舍弃遍历过的节点
}
}
}
拓展
细心的小伙伴会发现 queue = append(queue, []int{queue[0] + 1, queue[0] - 1, queue[0] * 2}...) 用了 ... 这个Go的语法糖,可能有些小伙伴没有见过(因为我也是在一次偶然的机会见到这个用法学习到的),这里拓展一下
‘…’ 其实是go的一种语法糖,用法有2个:
- 第一个用法主要是用于函数有多个不定参数的情况,可以接受多个不确定数量的参数
- 第二个用法是 slice 可以被打散进行传递
func test1(args ...string) { //可以接受任意个string参数
for _, v:= range args{
fmt.Println(v)
}
}
func main(){
var strss= []string{
"qwr",
"234",
"yui",
"cvbc",
}
test1(strss...) //切片被打散传入
}
结果:
qwr
234
yui
cvbc