当青训营遇上码上掘金-寻友之旅

78 阅读3分钟

当青训营遇上码上掘金,我参与的第五届青训营码上掘金活动,选择的是 寻友之旅 主题

主题内容

主题 3:寻友之旅

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

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

作者:青训营官方账号
链接:juejin.cn/post/718775…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

思路

  • 本问题在我看来是一个最短路径问题,这个题中的最短时间就是最短路径,我选择使用bfs算法求解,使用bfs算法需要构建队列,go没有现成的队列,所以我自己构建了个队列,因为有三种选择(+1,-1,*2),所以是bfs树是一个三叉树,树上每个点的深度就是这个点到根结点的最短距离,所以当搜索到终点时,树的深度就是起点到终点的最短路径,即小青到小码家所需的最短时间。

  • 我在构建队列时需要记录每个节点的层次信息,所以这个队列有两个动态数组,一个记录小青的移动位置坐标,一个记录移动位置的层次信息,当搜索到终点时,队尾元素的层次信息就是起点到终点的最短距离。

  • 在使用bfs时,我考虑到当起点在终点的右边时,移动方式只有-1,当起点在终点左边时,移动方式有+1,*2。所以我在搜索时也做了相应的调整。

  • 一个成熟编程人员应该学会水代码,所以我选择了通过构建队列,使用广度优先搜索算法解决这个问题。

  • 这东西让我写的复杂了,编程能力有限,这个问题也有很多其他的求解思路,我之后会再学习再改进的。

代码

package main

import (
   "errors"
   "fmt"
)

// 寻友之旅
// 三叉树 队列 广搜
// 树的深度即为最短时间
type queue struct { //头出尾入
   array []int //动态数组 存位置坐标
   level []int //记录层次信息
   head  int   //队头
   tail  int   //队尾
}

// 第一个元素是空开的
// 队头指向的是失效的数据 队尾指向的是有效的数据
func (q *queue) push(data int, leveldata int) {
   //因为使用动态数组创建的队列 无需考虑队列容量
   q.tail++
   q.array = append(q.array, data)      //q.array[q.tail] = data 因为使用的是切片 所以要使用append 追加元素 会自动扩容
   q.level = append(q.level, leveldata) //q.level[q.tail] = leveldata
   return                               //入队
}
func (q *queue) pop() (data int, leveldata int) {
   q.head++
   data = q.array[q.head]
   leveldata = q.level[q.head]
   return data, leveldata //队头移动,然后返回队头的数据
}

// 显示队列数据
func (q *queue) show() {
   //从队头遍历到队尾
   for i := 1; i <= len(q.array)-1; i++ {
      fmt.Println(i, q.array[i], " ", q.level[i])
   }
}
func (q *queue) init() {
   q.head = 0
   q.tail = 0
   q.array = make([]int, 1)
   q.level = make([]int, 1)

}
func (q *queue) isempty() bool {
   if q.head == q.tail {
      return true
   } else {
      return false
   }
}

var N, K int // 输入坐标青N 码K
var que = queue{
   array: make([]int, 1), //此处必须为1 因为动态数组扩容 索引就对不上了
   level: make([]int, 1),
   head:  0,
   tail:  0,
}

func main() {
   var t int
   fmt.Printf("请输入次数")
   fmt.Scanf("%d\n", &t)
   for t != 0 {
      que.init()
      fmt.Printf("请输入起点N和终点K:")
      fmt.Scanf("%d%d\n", &N, &K) //读取换行
      depth, err := bfs(N)
      if err != nil {
         fmt.Println(err.Error()) //显示错误信息
         return
      }
      fmt.Printf("最短时间为%d分钟\n", depth)
      //que.show()
      t--
   }
}

// 记录层次信息 父的层次信息+1就是子的层次信息
func bfs(loc int) (depth int, err error) { //广搜 层次遍历
   //先loc入队
   var levelsum = 0 //父节点层次信息
   que.push(loc, 0) //根结点0层
   var nowloc int
   for !que.isempty() { //不为空一直搜索
      nowloc, levelsum = que.pop()
      //三叉树+1 -1 *2
      //如果终点 在 当前点的左边 那只能 -1
      if nowloc > K {
         que.push(nowloc-1, levelsum+1) //当前坐标入队
         if nowloc-1 == K {
            return que.level[que.tail], nil //搜素完成 直接退出 返回队尾元素的层次信息
         }
      } else if nowloc < K { //如果终点 在 当前点的右边 可以 +1 *2
         que.push(nowloc+1, levelsum+1)
         que.push(nowloc*2, levelsum+1)
         if nowloc+1 == K || nowloc*2 == K {
            return que.level[que.tail], nil //搜素完成 直接退出 返回队尾元素的层次信息
         }
      } else { //父节点 正好就在终点 只有 N=K的时候
         return que.level[que.tail], nil //返回队尾元素的层次信息
      }
   }
   //为空的可能性 只有无解的时候 或者 起点就是终点的情况 而上面已经预防了
   return que.level[que.tail], errors.New("无解")
   //最终得到的队尾元素对应的就是最短的时间
}
```
```