思路:如果没有限制条件target和死亡锁deadends,那么这道题就转换为了排列组合,问题的关键是每次只能移动一个数字,并且是移动的数字与原有位置上的数字相差一个单位。那么针对一个数字串,有几种可能呢?一个数字可以向上向下下两种,那么一个字符串是4个字符组成,就转变成了一个字符串可以有八种可能。而针对这八种可能,以每个字符串作为基准元素,往下又有8种可能。所以这看上去就是BFS算法,而题目给了目标字符串target,我们的目的是要找到target,并且返回找到的这个target的最短路径,凡是求最短路径,那就是BFS的套路了。
我们回想一下BFS算法中,如何计算二叉树中最低的深度?
思路是:先把根节点放入到队列中,不断的从队列中拿出元素,递归终止条件就是当根节点的左右子树都为空时,那么就得到了最低深度的二叉树的值为多少?为了方便大家理解,我们将代码给出。
public int minDepth(TreeNode root) {
if(root==null) return 0;
Queue<TreeNode> q = new LinkedList<>();
int depth = 1;
q.offer(root);
while(!q.isEmpty()){
int size = q.size();
for(int i = 0; i< size;i++){
TreeNode curNode = q.poll();
// 判断是否到达终点
if(curNode.left == null && curNode.right == null){
return depth;
}if(curNode.left!=null){
q.offer(curNode.left);
}if(curNode.right!=null){
q.offer(curNode.right);
}
}
depth++;
}
return depth;
}
那么回到刚才的求转盘锁的问题,又何尝不是求BFS中路径最短的距离呢,说白了就是多叉树的深度问题。而由于转盘的数字转动过程中,可能会有已经访问过的,要求不能重复,所以我们定义一个set visited集合记录已经访问过得字符串,而对于deadends,我们仍然将其存放到另一个Set deads集合中,每次都要判断是否visited访问过了,若访问过了,则直接continue。
class Solution {
//首先当向上波动转盘时,会发生什么?
String upOne(String s,int j){
char[] ch = s.toCharArray();
if(ch[j] == '9') ch[j] = '0';
else{ ch[j] +=1;}
return new String(ch);
}
//首先当向下转动转盘时,会发生什么?
String downOne(String s,int j){
char[] ch = s.toCharArray();
if(ch[j] == '0') ch[j] = '9';
else{ ch[j] -=1;}
return new String(ch);
}
public int openLock(String[] deadends, String target) {
// 记录需要跳过的死亡密码
Set<String> deads = new HashSet<>();
for(String s:deadends) deads.add(s);
// 记录访问过的转盘字符串
Set<String> visited = new HashSet<>();
// 放置一个队列,让每个BFS的字符串进入到队列中
Queue<String> q = new LinkedList<>();
int step = 0;
q.add("0000");
visited.add("0000");
while(!q.isEmpty()){
int sz = q.size();
for(int i = 0;i<sz;i++){
String cur = q.poll();
if(deads.contains(cur)){
continue;
}
if(cur.equals(target)){
return step;
}
/* 将一个节点的未遍历相邻节点加入队列 */
for(int j=0;j<4;j++){
String up = upOne(cur,j);
if(!visited.contains(up)){
q.add(up);
visited.add(up);
}
String down = downOne(cur,j);
if(!visited.contains(down)){
q.add(down);
visited.add(down);
}
}
}
step++;
}
return -1;
}
}
class Solution {
//首先当向上波动转盘时,会发生什么?
String plusOne(String s,int j){
char[] ch = s.toCharArray();
if(ch[j] == '9') ch[j] = '0';
else{ ch[j] +=1;}
return new String(ch);
}
//首先当向下转动转盘时,会发生什么?
String minusOne(String s,int j){
char[] ch = s.toCharArray();
if(ch[j] == '0') ch[j] = '9';
else{ ch[j] -=1;}
return new String(ch);
}
int openLock(String[] deadends, String target) {
Set<String> deads = new HashSet<>();
for (String s : deadends) deads.add(s);
// 用集合不用队列,可以快速判断元素是否存在
Set<String> q1 = new HashSet<>();
Set<String> q2 = new HashSet<>();
Set<String> visited = new HashSet<>();
int step = 0;
q1.add("0000");
q2.add(target);
while (!q1.isEmpty() && !q2.isEmpty()) {
// 哈希集合在遍历的过程中不能修改,用 temp 存储扩散结果
Set<String> temp = new HashSet<>();
/* 将 q1 中的所有节点向周围扩散 */
for (String cur : q1) {
/* 判断是否到达终点 */
if (deads.contains(cur))
continue;
if (q2.contains(cur))
return step;
visited.add(cur);
/* 将一个节点的未遍历相邻节点加入集合 */
for (int j = 0; j < 4; j++) {
String up = plusOne(cur, j);
if (!visited.contains(up))
temp.add(up);
String down = minusOne(cur, j);
if (!visited.contains(down))
temp.add(down);
}
}
/* 在这里增加步数 */
step++;
// temp 相当于 q1
// 这里交换 q1 q2,下一轮 while 就是扩散 q2
q1 = q2;
q2 = temp;
}
return -1;
}
}