当青训营遇上码上掘金
当青训营遇上码上掘金 | 主题3:寻友之旅
题目描述
小青要找小码去玩,他们的家在一条直线上,当前小青在地点 N ,小码在地点 K ,并且小码在自己家原地不动等待小青。小青有两种交通方式可选:步行和公交。
- 步行:小青可以在一分钟内从任意节点 X 移动到节点 X-1 或 X+1
- 公交:小青可以在一分钟内从任意节点 X 移动到节点 2×X (公交不可以向后走)
请帮助小青通知小码,小青最快到达时间是多久?
输入格式
输入包含多组测试数据,每组数据占一行,包括两个整数 N 和 K,以空格分隔
输出格式
每组数据输出一行结果,是一个整数,表示小青到小码家所需的最短时间(以分钟为单位)
数据范围
0≤N , K≤100 000。
输入样例
5 17
9 5
0 15
3 3
输出样例
4
4
6
0
解题思路
本题可使用动态规划算法求解,具体分析思路如下。
- 状态表示:
f[i]- 集合:所有从
N到i的移动路径 - 属性:min,路径耗时的最小值
- 集合:所有从
- 状态计算:集合划分
- 当
0<=i<=N时,由于公交不可以向左走,所以只有步行一种交通方式可选,即只能向左步行N-i步,因此f[i] = N-i。 - 当
i>N时- 当
i是偶数时- 所有从
N到i的移动路径:f[i]- 最后一步是从左边向右步行过来的路径:
f[i-1]+1 - 最后一步是从左边向右乘公交过来的路径:
f[i/2]+1
- 最后一步是从左边向右步行过来的路径:
- 转移方程
f[i] = min(f[i-1]+1, f[i/2]+1)
- 所有从
- 当
i是奇数时- 所有从
N到i的移动路径:f[i]- 最后一步是从左边向右步行过来的路径:
f[i-1]+1 - 最后两步是先乘公交到
i+1再向左步行过来的路径:f[(i+1)/2]+2 - 最后两步是先乘公交到
i-1再向右步行过来的路径:f[(i-1)/2]+2
- 最后一步是从左边向右步行过来的路径:
- 转移方程
f[i] = min(f[i-1]+1, f[(i+1)/2]+2, f[(i-1)/2]+2)
- 所有从
- 当
- 当
- 具体实现
- 由于
f[i]只依赖于序号比它小的f值,所以按序号从小到大顺序遍历计算f数组,即从0遍历到K即可。 - C语言中的
while(scanf("%d",&n)!=EOF)在Go语言中应当如何实现?这里参考了go - Golang: Read ints from stdin until EOF while reporting format errors - Stack Overflow。 - Go语言没有内置整型变量的最大值、最小值函数,需要自己实现。这里参考了What is the correct way to find the min between two integers in Go? - Stack Overflow。
- 由于
完整代码
使用Go语言实现上述动态规划算法,完整代码如下,也可参见链接:当青训营遇上码上掘金 | 主题3:寻友之旅 - 码上掘金 (juejin.cn)。
package main
import (
"fmt"
"io"
)
const MAXN = 100010
var N int
var K int
var f [MAXN]int
func myMin(vars ...int) int {
ret := vars[0]
for _, i := range vars {
if ret > i {
ret = i
}
}
return ret
}
func main() {
for {
_, err := fmt.Scanf("%d %d", &N, &K)
if err == io.EOF {
break
}
if K <= N {
fmt.Printf("%d\n", N-K) // 只能向左步行
} else { // K>N
for i := 0; i <= N; i++ {
f[i] = N - i // 只能向左步行
}
for i := N + 1; i <= K; i++ {
if i%2 == 0 { // 最后一步是从左边向右步行过来、最后一步是从左边向右乘公交过来
f[i] = myMin(f[i-1]+1, f[i/2]+1)
} else { // 最后一步是从左边向右步行过来、最后两步是先乘公交到i+1再向左步行过来、最后两步是先乘公交到i-1再向右步行过来
f[i] = myMin(f[i-1]+1, f[(i+1)/2]+2, f[(i-1)/2]+2)
}
}
fmt.Printf("%d\n", f[K]) // 输出从起点N走到终点K所需的最短时间
}
}
}