当青训营遇上码上掘金
题目
寻友之旅
小青要找小码去玩,他们的家在一条直线上,当前小青在地点 N ,小码在地点 K (0≤N , K≤100 000),并且小码在自己家原地不动等待小青。小青有两种交通方式可选:步行和公交。
步行:小青可以在一分钟内从任意节点 X 移动到节点 X-1 或 X+1
公交:小青可以在一分钟内从任意节点 X 移动到节点 2×X (公交不可以向后走)
请帮助小青通知小码,小青最快到达时间是多久?
输入: 两个整数 N 和 K
输出: 小青到小码家所需的最短时间(以分钟为单位)
解答
这是一个求最短路径问题,可以使用广搜(BFS)解决。首先将小码的家作为终点,小青的家作为起点,然后开始搜索。在每一层遍历中,每一个点都可以通过步行或公交到达下一层的点。每次移动的时间都是1分钟,直到找到小码的家。最后输出到达小码的家所需的最短时间。
package main
import "fmt"
const MAX = 100001
func main() {
n, k := 0, 0
fmt.Scan(&n, &k)
// 用队列存储已经遍历过的点,用visted数组记录每个点是否已经遍历过
queue := make([]int, 0, MAX)
visted := make([]bool, MAX)
// 用distination数组记录每个点到起点的距离
distination := make([]int, MAX)
distination[n] = 0
// 初始化起点
visted[n] = true
queue = append(queue, n)
walk, bus, cur := 0, 0, 0
for len(queue) > 0 {
// 取出队头元素
cur = queue[0]
queue = queue[1:]
// 分别计算步行和公交到达的点
walk = cur - 1
bus = cur * 2
// 步行到达的点
if walk >= 0 && !visted[walk] {
visted[walk] = true
distination[walk] = distination[cur] + 1
queue = append(queue, walk)
}
// 公交到达的点
if bus < MAX && !visted[bus] {
visted[bus] = true
distination[bus] = distination[cur] + 1
queue = append(queue, bus)
}
}
fmt.Println(distination[k])
}
分析
上面的算法是一种使用广搜(BFS)求最短路径的方法,主要流程如下:
- 将小青的家作为起点,将小码的家作为终点。
- 使用队列存储已经遍历过的点,用 visited 数组记录每个点是否已经遍历过。
- 用 distination 数组记录每个点到起点的距离。
- 从起点开始遍历,每次从队列中取出一个点,分别计算步行和公交到达的点。
- 更新到达的点的距离,并将其加入队列中。
- 重复上述步骤直到找到终点。
- 输出终点到起点的距离。
这个算法的时间复杂度为 O(n),空间复杂度为 O(n)。
EXTRA
对于 最短路径算法 问题还有一个更加通用的算法 Dijkstra 算法
Dijkstra算法的一般解题过程如下:
- 初始化: 将起点的距离设置为0,其余点的距离设置为无穷大,用一个vis数组记录每个点是否已经遍历过。
- 将起点加入队列,并将其设为已遍历。
- 取出队列中距离最小的点,记为当前点。
- 更新当前点的相邻点的距离,如果可以通过当前点到达相邻点,并且这条路径更短,则更新相邻点的距离。
- 将所有未遍历过的相邻点加入队列,并将它们标记为已遍历。
- 如果当前点是终点,则结束遍历。
- 如果队列不为空,则回到步骤3。
- 输出终点的距离。
Dijkstra算法是一种贪心算法,它每次选择距离最小的点进行扩展,保证每次选择的路径都是最短的,因此算法的时间复杂度为O(n^2)。
在实际应用中Dijkstra算法可以使用堆优化来优化时间复杂度,这样算法的时间复杂度就能降为O(nlogn)。
详细 Dijkstra 算法