题目
https://leetcode-cn.com/problems/open-the-lock/
思路
双向BFS
代码
class Solution {
String t, s; // t是target,s是开始的状态
Set<String> set = new HashSet<>();
public int openLock(String[] _ds, String _t) {
s = "0000";
t = _t;
if (s.equals(t)) return 0;
for (String d : _ds) set.add(d);//死亡数字加入set
if (set.contains(s)) return -1;
int ans = bfs();
return ans;
}
int bfs() {
// d1 代表从起点 s 开始搜索(正向)
// d2 代表从结尾 t 开始搜索(反向)
Deque<String> d1 = new ArrayDeque<>(), d2 = new ArrayDeque<>();
/*
* m1 和 m2 分别记录两个方向出现的状态是经过多少次转换而来
* e.g.
* m1 = {"1000":1} 代表 "1000" 由 s="0000" 旋转 1 次而来 起点开始
* m2 = {"9999":3} 代表 "9999" 由 t="9996" 旋转 3 次而来 终点开始
*/
Map<String, Integer> m1 = new HashMap<>(), m2 = new HashMap<>();
d1.addLast(s);
m1.put(s, 0);
d2.addLast(t);
m2.put(t, 0);
/*
* 只有两个队列都不空,才有必要继续往下搜索
* 如果其中一个队列空了,说明从某个方向搜到底都搜不到该方向的目标节点
* e.g.
* 例如,如果 d1 为空了,说明从 s 搜索到底都搜索不到 t,反向搜索也没必要进行了
*/
while (!d1.isEmpty() && !d2.isEmpty()) {
int t = -1;
if (d1.size() <= d2.size()) {
t = update(d1, m1, m2);
} else {
t = update(d2, m2, m1);
}
if (t != -1) return t;
}
return -1;
}
int update(Deque<String> deque, Map<String, Integer> cur, Map<String, Integer> other) {
String poll = deque.pollFirst();//当前队列的第一个序列
char[] pcs = poll.toCharArray();//第一个序列的各个数字
int step = cur.get(poll);//到这个序列的步数
// 枚举替换哪个字符
for (int i = 0; i < 4; i++) {
// 能「正向转」也能「反向转」,这里直接枚举偏移量 [-1,1] 然后跳过 0
for (int j = -1; j <= 1; j++) {
if (j == 0) continue;
// 求得替换字符串 str
int origin = pcs[i] - '0';//序列的当前数字,转为INT
int next = (origin + j) % 10;//序列的当前数字旋转后的数字
if (next == -1) next = 9;
char[] clone = pcs.clone();//复制序列
clone[i] = (char)(next + '0');//复制序列的当前数字设为旋转后的当前数字
String str = String.valueOf(clone);//修改后的复制序列的字符串格式
if (set.contains(str)) continue;//如果修改后的是死亡数字
if (cur.containsKey(str)) continue;//如果修改后的,当前队列状态已存在
// 如果在「另一方向」找到过,说明找到了最短路,否则加入队列
if (other.containsKey(str)) {
return step + 1 + other.get(str);//到这个序列的步数+这一次+另一方向的次数
} else {
deque.addLast(str);//没找到过,将他加入当前队列
cur.put(str, step + 1);//将他加入当前队列的
}
}
}
return -1;
}
}