leetcode-752-打开旋转锁
这是我参与更文挑战的第3天,活动详情查看: 更文挑战
[博客链接]
[题目描述]
[题目链接]
[github地址]
[思路介绍]
思路一:BFS+记忆化存储
- 首先要确定边界条件什么情况无法满足target
- 1、非法target(题目中已经排除)
- 2、所有有可能变成target的数都被包含在deadends中
- 3、限制数组中包含0000
- 转变为target 只有通过target +-1 +-10 +-100 +-1000来做同
- 时保留特殊情况 当前位上数字为0、9的时候
- 根据题目可以预见每一个树的变换规律有8种可能,也就是所有分支节点8^4,我们可以根据所有1分支节点进行分叉、减支
- 上述的2情况也就是剪枝条件
- 从0000开始枚举所有变化,然后进行广度优先遍历
- 然后记录变化到当前位置的最少次数,最后当遍历完所有情况,返回结果集中的最小次数
- 如果无法满足结果集,就返回-1
- 这个思路的判断太过复杂 可以优化成思路二的判断方案
public int openLock(String[] deadends, String target) {
//将deadends存入set
Set<String> set = new HashSet<>();
Map<String, Integer> map = new HashMap<>();
if ("0000".equals(target)) {
return 0;
}
for (String dead : deadends
) {
if ("0000".equals(dead)) {
return -1;
}
set.add(dead);
}
Queue<String> queue = new LinkedList<>();
queue.add("0000");
map.put("0000", 0);
while (!queue.isEmpty()) {
String temp = queue.poll();
if (temp.equals(target)) {
return map.get(temp);
}
for (int i = 0; i < 4; i++) {
String pre = temp.substring(0, i);
String cur = temp.substring(i + 1, 4);
if (temp.charAt(i) == '0') {
if (!set.contains(pre + "1" + cur)) {
queue.add(pre + "1" + cur);
set.add(pre + "1" + cur);
map.put(pre + "1" + cur, map.get(temp) + 1);
}
if (!set.contains(pre + "9" + cur)) {
queue.add(pre + "9" + cur);
set.add(pre + "9" + cur);
map.put(pre + "9" + cur, map.get(temp) + 1);
}
} else if (temp.charAt(i) == '9') {
if (!set.contains(pre + "0" + cur)) {
queue.add(pre + "0" + cur);
set.add(pre + "0" + cur);
map.put(pre + "0" + cur, map.get(temp) + 1);
}
if (!set.contains(pre + "8" + cur)) {
queue.add(pre + "8" + cur);
set.add(pre + "8" + cur);
map.put(pre + "8" + cur, map.get(temp) + 1);
}
} else {
String key1 = pre + (Integer.parseInt(temp.substring(i, i + 1)) + 1) + cur;
if (!set.contains(key1)) {
queue.add(key1);
set.add(key1);
map.put(key1, map.get(temp) + 1);
}
String key2 = pre + (Integer.parseInt(temp.substring(i, i + 1)) - 1) + cur;
if (!set.contains(key2)) {
queue.add(key2);
set.add(key2);
map.put(key2, map.get(temp) + 1);
}
}
}
}
return -1;
}
时间复杂度O( + md) b为10(进制)d为4 锁的位数 m为边界数组长度
思路二:判断条件优化
public int openLock(String[] deadends, String target) {
//将deadends存入set
Set<String> set = new HashSet<>();
Map<String, Integer> map = new HashMap<>();
for (String dead : deadends
) {
if ("0000".equals(dead)) {
return -1;
}
set.add(dead);
}
Queue<String> queue = new LinkedList<>();
queue.add("0000");
map.put("0000", 0);
int size = 1;
while (!queue.isEmpty()) {
String temp = queue.poll();
if (temp.equals(target)) {
return map.get(temp);
}
char[] c = temp.toCharArray();
for (int i = 0; i < 4; i++) {
char tempC = c[i];
int x = tempC - '0';
int y = x;
c[i] = (char) (x + 1 > 9 ? '0' : x + 1 + '0');
String s = String.valueOf(c, 0, 4);
if (!set.contains(s)) {
queue.offer(s);
set.add(s);
map.put(s, map.get(temp) + 1);
}
c[i] = (char) (y - 1 < 0 ? 9 + '0' : y - 1 + '0');
s = String.valueOf(c, 0, 4);
if (!set.contains(s)) {
queue.offer(s);
set.add(s);
map.put(s, map.get(temp) + 1);
}
if (s.equals(target)) {
return map.get(target);
}
c[i] = tempC;
}
}
return -1;
}
时间复杂度O( + md) b为10(进制)d为4 锁的位数 m为边界数组长度
思路三:双向BFS
- 单向BFS的优化难度在于空间复杂度,随着queue的增多,会导致大量内存消耗
- 当我们单向广度优先时,时间复杂度为O( + md)**
- 双向遍历优化的更多是空间复杂度
- 双向BSF的思路其实有点类似二分查找的思路,就是由从起点的单向扫描,变成起点和终点相向而行的扫描过程
- 我们首先要依据单向扫描的方案对分别对起点和终点进行扫描
- 但是本题 为了保证两边扫描的次数基本相同,可以通过对queue的大小进行区分扫描
- 准确的说双向扫描更像是一种离散式的扫描,他将BFS从单向,变成离散式的扫描,而不是再依从每个节点的连续顺序
- 如果一侧map包含了另一侧queue当前弹出元素,则表示两边节点相遇,返回两边节点的value之和 + 1
class Solution {
Set<String> set = new HashSet<>();
Set<String> traced = new HashSet<>();
public int openLock(String[] deadends, String target) {
//corner case
if ("0000".equals(target)){
return 0;
}
Map<String, Integer> rmap = new HashMap<>(), lmap = new HashMap<>();
Deque<String> rqueue = new ArrayDeque<>(), lqueue = new ArrayDeque<>();
for (String s : deadends
) {
if (s.equals(target) || "0000".equals(s)) {
return -1;
}
set.add(s);
}
rqueue.add(target);
lqueue.add("0000");
lmap.put("0000", 0);
rmap.put(target, 0);
while (!lqueue.isEmpty() && !rqueue.isEmpty()) {
int t = -1;
if (lqueue.size() <= rqueue.size()) {
t = trace(lqueue, lmap, rmap);
} else {
t = trace(rqueue, rmap, lmap);
}
if (t != -1) {
return t;
}
}
return -1;
}
public int trace(Deque<String> queue, Map<String, Integer> cur, Map<String, Integer> other) {
String temp = queue.pollFirst();
if (other.containsKey(temp)) {
return other.get(temp) + cur.get(temp) + 1;
}
for (int i = 0; i < 4; i++) {
char[] c = temp.toCharArray();
char tempC = c[i];
int x = tempC - '0';
int y = x;
c[i] = (char) (x + 1 > 9 ? '0' : x + 1 + '0');
String s1 = String.valueOf(c, 0, 4);
if (!set.contains(s1)){
if (other.containsKey(s1)) {
return other.get(s1) + cur.get(temp) + 1;
}else{
if (!traced.contains(s1)){
queue.addLast(s1);
}
traced.add(s1);
cur.put(s1, cur.get(temp) + 1);
}
}
c[i] = (char) (y - 1 < 0 ? 9 + '0' : y - 1 + '0');
String s2 = String.valueOf(c, 0, 4);
if (!set.contains(s2)){
if (other.containsKey(s2)) {
return other.get(s2) + cur.get(temp) + 1;
} else {
//判断当前节点是否被扫描过
if (!traced.contains(s2)){
queue.addLast(s2);
}
traced.add(s2);
cur.put(s2, cur.get(temp) + 1);
}
}
}
return -1;
}
}
时间复杂度O( + md)**
剩下的两个算法实在是超出我理解范畴了