一、题目描述
你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’ 。每个拨轮可以自由旋转:例如把 ‘9’ 变为 ‘0’,‘0’ 变为 ‘9’ 。每次旋转都只能旋转一个拨轮的一位数字。
锁的初始数字为 ‘0000’ ,一个代表四个拨轮的数字的字符串。
列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。
字符串 target 代表可以解锁的数字,你需要给出最小的旋转次数,如果无论如何不能解锁,返回 -1。
二、解题思路
先来分析题目,转盘锁带有四个拨轮,每个拨轮每次可以向上或者向下拨,也就是说当前转盘锁的状态转变到下一个状态有8种可能的选择,以’0000’为例:
对于当前的某种状态需要遍历每一种它的下一个状态,如果将这抽象成一张图这就很类似于广度优先搜索了(广度优先所有在我的LeetCode刷题笔记的第31题—二叉树的层次遍历有详细介绍,这里就不赘述了),像是在该图上寻求最短距离。
另外注意一个条件:就是想要拨到target的代表的解锁数字,经过的过程必须跳过可能出现的deadends包含的死亡数字。
target为’0000’时返回-1。
三、代码演示
class Solution {
public int openLock(String[] deadends, String target) {
//使用一个set集合,存放死亡数组中的密码值
Set<String> deads = new HashSet<>();
for(String s:deadends){
//遍历死亡数组中的值,存入deads集合
deads.add(s);
}
//用来判断某一密码值相邻密码值是否重复出现,防止死循环
Set<String> visited = new HashSet<>();
//Queue队列,广度优先搜索会用到的,存储当前状态所对应下一个状态的8种情况
Queue<String> queue = new LinkedList();
queue.add("0000");
visited.add("0000");
//声明初始步数,记录共拨了几次转盘
int step = 0;
//当所有可能的密码值都转到时结束
while(!queue.isEmpty()){
int len = queue.size();
//遍历队列中的密码值
for(int i=0; i<len; i++){
//poll方法为出队操作,将8中情况对应的值依次出队
String s = queue.poll();
//如果死亡数组中包含密码值,跳过循环,因为这时密码值已经被锁定,不能进行下一步。
if(deads.contains(s)){
continue;
}
//遍历到某一步的值等于target,返回step
if(s.equals(target)){
return step;
}
//遍历当前密码值的相邻8个密码值并作判断是否重复出现
//这里的4指的是4个拨轮,每个拨轮都有向上和向下拨的两种状态
for(int j=0; j<4; j++){
//调用up方法,判断每个拨轮向上拨可能出现的情况
String up = up(s, j);
String down = down(s, j);
if(!visited.contains(up)){
queue.add(up);
visited.add(up);
}
if(!visited.contains(down)){
queue.add(down);
visited.add(down);
}
}
}
//当前图的第一层遍历
step++;
}
//无解,返回-1
return -1;
}
//该方法将轮盘的每个拨轮向上拨
public String up(String s, int j){
//声明一个字符串数组存放当前的拨轮位置的字符数字
char[] c = s.toCharArray();
if(c[j]=='9'){
c[j] = '0';
}else{
//当前拨轮对应数字加一
c[j]++;
}
return new String(c);
}
//该方法将轮盘的每个拨轮向下拨
public String down(String s, int j){
//声明一个字符串数组存放当前的拨轮位置的字符数字
char[] c = s.toCharArray();
if(c[j]=='0'){
c[j] = '9';
}else{
//当前拨轮对应数字减一
c[j]--;
}
return new String(c);
}
}
代码部分详解:
第11行:为什么要声明一个set集合存放某一密码值相邻密码值是否重复出现,防止死循环呢?如果只存在一个队列来存放当前位置的8种情况,会出现“走回头路的情况”,假如初始位置是’0000’,那么当拨到’1000’时将它放到队列里面,但是当’1000’被拿出队列,这是需要其它7种情况对应的数字,这是又要拨回去从’0000’开始,它又会对应8情况,这样会陷入死循环。所以这时候需要新的集合,存储那些已经遍历的集合:
1.若当前新拨出来的密码没有出现过,则将该密码加入到队列和 visited 集合中;
2.若当前新拨出来的密码已经在之前的遍历过程中出现过,此时不需要进行任何操作,继续遍历被拨密码的下一个位置即可。
集合,存储那些已经遍历的集合: