题目描述
题目来源于leetcode
你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有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’ 中产生。
分析
对于每个密码有8种操作(一位2种操作,密码有4位),你可以把本此题看作一颗树处理,把target作为根节点,8种操作作为子节点,进行广度优先遍历,直到得到“0000”为止。遍历过程中不能把deadends中的死亡数字作为根节点,也不能将已经被遍历过的节点作为根节点,所以可以把已经遍历过的密码加入到deadends中,确保每次遍历过程中都得到的是新的密码以及不是死亡密码,遍历过程就是这样。
如何得到结果呢?我们需要定义一个result作为结果,result的值就是树的深度-1。那如何判断遍历时从上一层到达了下一层,也就是什么时result++呢?我们只需要在每一层的末尾添加一个null,当遍历得到的结果为null是,说明要进入下一层了,此时执行result++。
代码
class Solution {
/*
* 定义移动单个拨轮+1的操作
*
*/
public static char actionAdd(char i) {
char j = '0';
int temp = i - '0';
if(i>='0' && i<='8') {
j = (char) (temp + 1 + '0');
}
return j;
}
/*
* 定义移动单个拨轮-1的操作
*
*/
public static char actionSubtract(char i) {
char j = '0';
int temp = i - '0';
if(i>='1' && i<='9') {
j = (char) (temp - 1 + '0');
}
if(i == '0') {
j = '9';
}
return j;
}
/*
* 定义分别移动四个拨轮的操作,执行此操作后可得新的8种转盘锁的状态
*
*/
public static List<String> action(String str) {
List<String> list = new ArrayList<>();//定义动态数组存储8种结果
char[] s = str.toCharArray();
char[] m = Arrays.copyOf(s, s.length);
for(int i=0;i<s.length;i++) {
s[i] = actionAdd(s[i]);
list.add(new String(s));
s = Arrays.copyOf(m, m.length);//每一次操作都是在原状态下进行的,所以每次操作完后重置为原状态
s[i] = actionSubtract(s[i]);
list.add(new String(s));
s = Arrays.copyOf(m, m.length);
}
return list;
}
public int openLock(String[] deadends, String target) {
Set<String> dead = new HashSet();//定义Hashset来方便查找8种结果中是不是存在死亡数字
for(String str:deadends) dead.add(str);
int result = 0;
Queue<String> queue= new LinkedList<>();
queue.add(target);
queue.add(null);
while(!queue.isEmpty()) {
String s = queue.poll();
if(s==null) {
result++;
if(queue.peek()!=null)
queue.offer(null);//在每一层遍历完后,添加一个null,只要s=null就说明即将处于下一层了,结果+1
}else if(s.equals("0000")){
return result;
}else{
dead.add(s);//把已经遍历过的密码添加进dead
List<String> l = new ArrayList<>();
l = action(s);
for(String x : l) {
if(!dead.contains(x)) {//确认x是新的未遍历过的密码并且不是死亡数字
queue.add(x);
dead.add(x);//此时的x已经遍历得到过,加入dead
}
}
}
}
return -1;
}
}
相关知识
LinkedList的poll()方法:检索并删除此列表的头部(第一个元素),返回值是被删除的元素
LinkedList的peek()方法:检索但不删除此列表的头部(第一个元素),返回值是第一个元素
LinkedList的offer(E e)方法:将指定的元素添加为此列表的尾部(最后一个元素)。
HashSet的contains(Object o) 方法:如果此set包含指定的元素,则返回 true 。
Arrays类的copyOf()方法和copyOfRange()方法用来复制数组:
String string = "abc";
char[] str = string.toCharArray();
char[] newstr1 = Arrays.copyOf(str, str.length);
char[] newstr2 = Arrays.copyOfRange(str, 0, 3);
Arrays 类的 copyOf() 方法的语法格式如下:
Arrays.copyOf(dataType[] srcArray,int length);
其中,srcArray 表示要进行复制的数组,length 表示复制后的新数组的长度。
使用这种方法复制数组时,默认从源数组的第一个元素(索引值为 0)开始复制,目标数组的长度将为 length。如果 length 大于 srcArray.length,则目标数组中采用默认值填充;如果 length 小于 srcArray.length,则复制到第 length 个元素(索引值为 length-1)即止。
Arrays 类的 CopyOfRange() 方法是另一种复制数组的方法,其语法形式如下:
Arrays.copyOfRange(dataType[] srcArray,int startIndex,int endIndex)
其中,srcArray 表示源数组;startIndex 表示开始复制的起始索引,目标数组中将包含起始索引对应的元素,另外,startIndex 必须在 0 到 srcArray.length 之间;endIndex 表示终止索引,目标数组中将不包含终止索引对应的元素,endIndex 必须大于等于 startIndex,可以大于 srcArray.length,如果大于 srcArray.length,则目标数组中使用默认值填充。