1. 问题描述:
你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有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
解释:
无法旋转到目标数字且不被锁定。
示例 4:
输入: deadends = ["0000"], target = "8888"
输出:-1
提示:
死亡列表 deadends 的长度范围为 [1, 500]。
目标数字 target 不会在 deadends 之中。
每个 deadends 和 target 中的字符串的数字会在 10,000 个可能的情况 '0000' 到 '9999' 中产生。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/open-the-lock
2. 思路分析:
① 根据题目可以知道,从一开始的原始状态"0000"到最终的目标状态的最短路径是多少,并且题目中给定了在中间转动过程中不能到达的状态,这个实际上就是使用bfs宽搜解决的问题(从原始状态到目标状态的最短路径问题求解),这道题目只是多了一些中间的限制条件也就是不能够到达给出的那些状态,对于这些状态我们可以在中间过程中进行判断忽略掉即可
② bfs宽搜主要是借助于队列来实现,利用了队列先进先出的特点,将当前状态下的邻居节点加入即可,对于这道题目来说实际上就是通过转换四位字符串的数字来实现,对于java来说我们可以声明一个内部类,里面封装几个属性,这样的话可以很方便记录中间过程:
1)当前的状态是什么
2)到达当前状态的步数是多少
3)到达当前状态的路径是什么,对于这个属性主要是为了在测试的时候输出到达最终的目标状态的路径是什么看一下生成的最短路径是否正确,这个借鉴的是迷宫的生成的最短路径记录的方法
③ 在一开始的时候我们可以将初始状态加入到队列中,在队列不为空的情况下执行循环,将当前的状态进行处理,对得到的四位数字的字符串进行加1减1的操作,这里可以使用一个方法来生成一个数组,对当前状态的每一位数字进行加1减1的操作,并且在对数字9与数字0在转换的过程中进行判断,最好在写完这个方法之后测试一下当前状态下生成的数组是否正确这样在确保这一步没有错的情况下下一步就简单了
④ 使用Set来去重,因为在中间加1减1的过程中有可能重现重复的状态需要使用set来进行判断在之前没有这个状态的情况下再将这个状态加入进来,并且需要判断一下到达当前状态的过程中有没有与给出的死亡数字是一样的,假如不一样才可以将状态加入进来
⑤ 实际上这个是bfs的典型应用,套用基本的模板即可,中间增加一些判断代码即可完成,需要注意中间的一些细节处理,需要注意使用全局变量之后需要将其中的static修饰符去掉否则提交代码上去是错误的
3. 代码如下:
import java.util.*;
public class Solution {
/*使用内部类来创建节点, 里面存放到达当前节点的字符串, 到达当前字符串的步数,到达当前状态的路径*/
public static class Node{
private String cur;
private int step;
private String road;
public String getCur() {
return cur;
}
public void setCur(String cur) {
this.cur = cur;
}
public int getStep() {
return step;
}
public void setStep(int step) {
this.step = step;
}
public String getRoad() {
return road;
}
public void setRoad(String road) {
this.road = road;
}
/*构造器: 用来初始化*/
public Node(String cur, int step) {
this.cur = cur;
this.step = step;
}
}
/*使用Set来去重,避免重复的状态计算, 注意在提交的时候不要加上static否则虽答案是错误的*/
Set<String> set = new HashSet<>();
public int openLock(String[] deadends, String target) {
for (int i = 0; i < deadends.length; ++i){
if (deadends[i].equals("0000")) return -1;
}
/*思路是对于思路数字进行加1减去1的操作*/
Queue<Node> queue = new LinkedList();
set.add("0000");
Node start = new Node("0000", 0);
start.setRoad("0000");
queue.add(start);
while (!queue.isEmpty()){
Node poll = queue.poll();
if (poll.getCur().equals(target)){
//System.out.println(poll.getRoad());
return poll.getStep();
}
String node[] = make(poll.cur);
for (int i = 0; i < node.length; ++i){
/*System.out.println(node[i]);*/
if (!set.contains(node[i])){
int flag = 1;
for (int j = 0; j < deadends.length; ++j){
if (deadends[j].equals(node[i])){
flag = 0;
break;
}
}
if (flag == 1){
Node n = new Node(node[i], poll.step + 1);
n.setRoad(poll.getRoad() + "-->" + node[i]);
queue.add(n);
set.add(node[i]);
}
}
}
}
return -1;
}
public static String[] make(String cur) {
/*对字符串进行加1减1的操作*/
String node[] = new String[cur.length() * 2];
int count = 0;
String t = "";
for (int i = 0; i < cur.length(); ++i){
int c = cur.charAt(i) - '0';
int c1 = 0, c2 = 0;
if (c == 9) c1 += 0;
else {
c1 = (c + 1);
}
/*注意截取的位置是从第i + 1个位置开始*/
node[count++] = cur.substring(0, i) + c1 + cur.substring(i + 1);
t = "";
if (c == 0) c2 += 9;
else {
c2 = c - 1;
}
node[count++] = cur.substring(0, i) + c2 + cur.substring(i + 1);
}
return node;
}
}