当青训营遇上码上掘金
代码见文末
这是一道典型的 BFS 问题,由于小青有两种移动方式,所以我们需要在 BFS 的时候将两种移动方式都考虑进去。
我们可以把小青当前所在的位置看作一个点,用一个队列来存储已经被访问过的点,并且用一个数组来存储每个点到起点的时间。
在 BFS 的过程中,每次取出队列的第一个点,并且将两种移动方式能到达的下一个点加入到队列中,同时更新数组中存储的时间。
当取出的点正好是目标点时, 返回时间
考虑到有的同学还没有了解BFS, 可能不明白为什么这样能够得到正确答案, 这里用一个简单的例子帮助同学们理解
假设小青从n=200出发, 并且不会走同一个点两次
t=1时, 我们能到达的有199, 201, 400
t=2时, 能到达的没走过的点有198, 398, 202, 402, 399, 401, 800
假设t=x时, 到达的没走过点中正好有我们的目标点, 那个x不就是我们要找的最短时间吗?
为什么? 因为我们是从时间由短到长进行遍历的啊, 到达目标点的时间不可能小于x了, x就是到达目标点的最短时间
队列的使用
队列可以类比日常生活中的队列, 先进先出
代码中, 我们先将出发点加入队列
每轮循环从队列首部取出一个点
然后将时间上离该点最近的点都加入队列, 将即1分钟可以到达的点加入队列
这样, 保证了时间距离小的点先入队列, 先出队列, 先被访问到
保证了我们是按时间由短到长进行遍历
为什么不用递归
递归和 BFS 都可以用来搜索图上的路径,但是它们在实现和思想上有很大的不同。
递归的思想是 “自上而下”,它从当前点开始递归,每次调用递归函数都是在深度优先地搜索。递归的优点是简单易懂,缺点是可能会爆栈。
而 BFS 的思想是 “自下而上”,它从起点开始广度优先搜索,每次取出队列中距离最近的点来更新。BFS 的优点是可以保证找到的路径是最短路径,并且不会爆栈。
对于这道题来说,我们需要找到小青从起点到目标点的最短路径,那么 BFS 更适合这种情况。
如果我们使用递归,我们只能深度优先搜索,不能保证找到的路径一定是最短的,并且递归的深度可能会很大,导致爆栈。
代码 - 找到最短时间
⭐代码中dist存的不是空间距离, 而是时间距离, 即dist[i]表示N到i所需的最短时间. 怕同学们误会, 所以解释一下
package main
import "fmt"
func minSteps(n int, k int) int {
// 初始化队列
var queue []int
queue = append(queue, n)
// 初始化距离数组
var dist = make([]int, 100001)
for i := range dist {
dist[i] = -1
}
dist[n] = 0
// BFS
for len(queue) > 0 {
// 取出队列的第一个点
cur := queue[0]
queue = queue[1:]
// 步行
if cur-1 >= 0 && dist[cur-1] == -1 {
dist[cur-1] = dist[cur] + 1
queue = append(queue, cur-1)
}
if cur+1 < 100001 && dist[cur+1] == -1 {
dist[cur+1] = dist[cur] + 1
queue = append(queue, cur+1)
}
// 公交
if cur*2 < 100001 && dist[cur*2] == -1 {
dist[cur*2] = dist[cur] + 1
queue = append(queue, cur*2)
}
// 如果到达目标点,返回距离
if cur == k {
return dist[k]
}
}
return -1
}
func main() {
n := 3
k := 11
fmt.Println(minSteps(n, k))
}
代码 - 找到最短时间, 并输出最短时间对应的那条路径
用parent数组存储节点的父节点, 这样, 最后就可以从目标点一直找回出发点, 得到路径
package main
import "fmt"
func minSteps(n int, k int) (int, []int) {
// 初始化队列
var queue []int
queue = append(queue, n)
// 初始化距离数组
var dist = make([]int, 100001)
for i := range dist {
dist[i] = -1
}
dist[n] = 0
// 初始化父节点数组
var parent = make([]int, 100001)
for i := range parent {
parent[i] = -1
}
parent[n] = n
// BFS
for len(queue) > 0 {
// 取出队列的第一个点
cur := queue[0]
queue = queue[1:]
// 步行
if cur-1 >= 0 && dist[cur-1] == -1 {
dist[cur-1] = dist[cur] + 1
parent[cur-1] = cur
queue = append(queue, cur-1)
}
if cur+1 < 100001 && dist[cur+1] == -1 {
dist[cur+1] = dist[cur] + 1
parent[cur+1] = cur
queue = append(queue, cur+1)
}
// 公交
if cur*2 < 100001 && dist[cur*2] == -1 {
dist[cur*2] = dist[cur] + 1
parent[cur*2] = cur
queue = append(queue, cur*2)
}
// 如果到达目标点,返回距离
if cur == k {
var path []int
cur := k
for cur != n {
path = append(path, cur)
cur = parent[cur]
}
path = append(path, n)
// 翻转路径
for i := 0; i < len(path)/2; i++ {
path[i], path[len(path)-i-1] = path[len(path)-i-1], path[i]
}
return dist[k], path
}
}
return -1, nil
}
func main() {
n := 3
k := 10000
steps, path := minSteps(n, k)
fmt.Println("steps:", steps)
fmt.Println("path:", path)
}