寻友之旅

98 阅读4分钟

当青训营遇上码上掘金

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