leetcode每日一题系列-打开转盘锁

215 阅读2分钟

leetcode-752-打开旋转锁

这是我参与更文挑战的第3天,活动详情查看: 更文挑战

[博客链接]

一个菜🐔的学习之路

掘金首页

[题目描述]

[题目链接]

leetcode题目链接

[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(bdd2b^d \cdot d^2 + 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(bdd2b^d \cdot d^2 + md) b为10(进制)d为4 锁的位数 m为边界数组长度


思路三:双向BFS

  • 单向BFS的优化难度在于空间复杂度,随着queue的增多,会导致大量内存消耗
  • 当我们单向广度优先时,时间复杂度为O(bdd2b^d \cdot d^2 + 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(bdd2b^d \cdot d^2 + md)**

剩下的两个算法实在是超出我理解范畴了