当前:LC752 · 打开转盘锁 · 首次出现于 Day 34 · 路径:顶栏「56天打卡」→ 点击 LC 题号 → 逐题动画

LC752 · Open the Lock · BFS

打开转盘锁:密码当点,拨一格当边

四位数字,每位能往前拨或往后拨一格,0 和 9 连成环。从 0000 一层层拨出去,死亡密码当作墙,先碰到目标的层数就是最少步。

每个密码有 8 个邻居(四位各 ±1,0 与 9 环回)。从 0000 做层序 BFS,死亡数字和已访问的点跳过。第一次到达 target 的层数即最少拨动次数;起点就是死亡数字或走尽仍未到达则返回 -1。时间与空间都按 10⁴ 个状态计。

时间 O(10⁴·8)空间 O(10⁴)结论先行 · 全文约 6 节
导读

锁上有四个转轮,每位是 0 到 9,可以朝两个方向拨一格,9 再往前变成 0,0 再往后变成 9。每次只拨一位。给你目标密码,以及若干死亡数字:指针走到这些密码上锁就卡死,这条路作废。求从 0000 拨到目标的最少次数;没法到达返回 -1。

主例 target = 0202,死亡数字含 0201、0101、0102、1212、2002。一条合法路线是 0000→1000→1100→1200→1201→1202→0202,一共 6 步。看起来更短的 0000→0001→0002→0102→0202 会在 0102 卡死。

密码只有一万种,每次 8 个邻居,这是一张有限的无权图。本文要回答:为什么最短拨动次数等于 BFS 层数,第一步那 8 个邻居是谁,以及死亡数字怎样把近路堵死。

8 个邻居按层扩散

第一直觉是对着目标一位一位拨:要把 0000 变成 0202,第二位拨两次似乎就够。可是路上可能撞上死亡数字,而且有时得先往远处绕,再转回来。深度优先搜索能找到某条合法路,但不保证最短。每位独立贪心,也处理不了「中间那位不能经过 0102」。

缺的是一张把状态当点的图。一个四位字符串就是一个点。从任意点出发,选一位加一或减一,得到 8 个邻居,这就是 8 条边。0 减一变成 9,9 加一变成 0,边在数字上是环形的。所有拨动代价都是 1,最短拨动次数就是无权图最短路,用队列一层一层扩。

死亡数字相当于墙上的点:生成邻居时看见它就当没这条边。已经进来过的密码也要记下,否则在一万个点上来回拨会转圈。起点 0000 若本身是死亡数字,一步都迈不出去,直接 -1。目标就是 0000,一步不用拨,返回 0。

用手走主例。步 0 只有 0000,演示第一帧。拨一步,四位各加一、各减一,得到 0001、0010、0100、1000、9000、0900、0090、0009;这一层 8 个都不是死亡数字,演示第二帧全部列出。

之后继续按层扩。想走 0001→0002→0102 的人会在 0102 被挡,0201、2002 也会挡住另一些近路。队列第一次把 0202 当作邻居拿出来时,层数是 6。一条没撞墙的路是 0000、1000、1100、1200、1201、1202、0202。

密码空间封顶一万,每点最多扩 8 次,搜索一定结束。队列空了还没见过目标,返回 -1。双向 BFS 从起点和目标同时扩,能减少相遇之前的层数,但教学上单向层序已经能把「步数 = 层数」说清楚。不要用访问数组之前先拨再判断死亡,否则可能把死亡数字入队,后续又从墙上往外走。

状态图点是密码,边是拨一格。边权全是 1,所以 BFS 的层数就是最少步。死亡数字不是「到了再回头」,而是根本不进队列。主例 6 步是绕开 0102、0201 之后的最短路。
target=0202
target0202
0 层可达
0000
步 0:0000

Go:状态图 BFS

solution.goGo
func openLock(deadends []string, target string) int {
dead := map[string]bool{}
for _, d := range deadends { dead[d] = true }
if dead["0000"] { return -1 }
if target == "0000" { return 0 }
q := []string{"0000"}
visited := map[string]bool{"0000": true}
steps := 0
for len(q) > 0 {
steps++
size := len(q)
for i := 0; i < size; i++ {
cur := q[0]; q = q[1:]
for _, nb := range neighbors(cur) {
if nb == target { return steps }
if dead[nb] || visited[nb] { continue }
visited[nb] = true
q = append(q, nb)
}
}
}
return -1
}

1先把死亡数字放进集合。起点就在集合里,直接 -1;目标就是起点,直接 0。

2外层 steps++ 再按 size 切层。主例第一层扩出那 8 个邻居,steps 变成 1;第一次把 0202 当邻居看见时 steps 是 6。

3邻居是死亡或访问过就跳过,不要入队。从墙上出发会数出一条不合法的短路径。

总结

密码当点、拨一格当边,BFS 层数即最少步。主例绕开死亡数字要 6 步。

  • 每位 ±1 共 8 个邻居,0 和 9 环回。主例第一步就是那 8 个串。
  • 0102、0201 这类死亡数字不入队。贪心少拨几位会撞墙。
  • 起点是死亡数字返回 -1。搜完没有目标也返回 -1。
同族题目
LC127单词接龙LC1091二进制矩阵最短路径LC433最小基因变化