题目链接:
https://leetcode.cn/problems/open-the-lock/
你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 。每个拨轮可以自由旋转:例如把 '9' 变为 '0','0' 变为 '9' 。每次旋转都只能旋转一个拨轮的一位数字。
锁的初始数字为 '0000' ,一个代表四个拨轮的数字的字符串。
列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。
字符串 target 代表可以解锁的数字,你需要给出解锁需要的最小旋转次数,如果无论如何不能解锁,返回 -1 。
示例 1:
输入:deadends = ["0201","0101","0102","1212","2002"], target = "0202"
输出:6
解释:
可能的移动序列为 "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202"。
注意 "0000" -> "0001" -> "0002" -> "0102" -> "0202" 这样的序列是不能解锁的,
因为当拨动到 "0102" 时这个锁就会被锁定。
示例 2:
输入: deadends = ["8888"], target = "0009"
输出:1
解释:把最后一位反向旋转一次即可 "0000" -> "0009"。
示例 3:
输入: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
输出:-1
解释:无法旋转到目标数字且不被锁定。
每一位可以往上或往下拨1,一共有四位,每个转盘状态的下一个状态最多对应(=2*4)8种情况
说明:要除去deadends中的组合,以及拨到过的组合
这是一个多叉树的问题(层数和拨动次数对应),可以使用层序遍历,即BFS来找最短路径
维护一个队列,一层层加子节点,如果子节点中有目标就返回
细节问题:
如果起点在deadends,开局就挂,返回-1
如果目标就是起点,开局躺赢,返回0
class Solution:def openLock(self, deadends: List[str], target: str) -> int:#用一个字典存相邻的数neighbor = {}for i in range(10):neighbor[str(i)] = [str((i+9)%10),str((i+11)%10)]if "0000" in deadends:return -1if target == "0000":return 0q = ["0000"]level = 0#搜索过的节点vis = {"0000"}while q:n = len(q)for i in range(n):tmp = q.pop(0)L = list(tmp)children = []for j in range(4):num = L[j]L[j] = neighbor[num][0]children.append("".join(L))L[j] = neighbor[num][1]children.append("".join(L))L[j] = numfor child in children:if child == target:return level + 1if child not in deadends and child not in vis:vis.add(child)q.append(child)level += 1return -1
适用场景:知道target是什么、正序和反序搜索行为一样(这样会比较简单)
也可以同时从起点和终点搜索,随着层级越多BFS维护的队列会越来越长,搜索的越来越广。从两端同时搜索,可以减少多叉树下半部分的搜索广度。本题知道起点和终点,反向搜索也是上或下拨一。
细节问题:
维护两个队列q和q2,交替搜索下一层,可以每搜索完一层交换两个队列(q,q2 = q2,q),即形式上一直搜索的是q,每次查看搜索出来的子节点是否在q2中(在则相遇了,返回步数)
class Solution:def openLock(self, deadends: List[str], target: str) -> int:neighbor = {}for i in range(10):neighbor[str(i)] = [str((i+9)%10),str((i+11)%10)]if "0000" in deadends:return -1if target == "0000":return 0q = ["0000"]level = 0#搜索过的节点vis = {"0000",target}q2 = [target]while q and q2:n = len(q)for i in range(n):tmp = q.pop(0)L = list(tmp)children = []for j in range(4):num = L[j]L[j] = neighbor[num][0]children.append("".join(L))L[j] = neighbor[num][1]children.append("".join(L))L[j] = numfor child in children:if child in q2:return level + 1if child not in deadends and child not in vis:vis.add(child)q.append(child)level += 1q,q2 = q2,qreturn -1
执行时间会快一些