752. Open the turntable lock

June 25, 2021 leetcode daily question

Rebate website https://www.cpa5.cn/

Link: https://leetcode-cn.com/problems/open-the-lock/

Tags: breadth first search, array, hash table, string

subject

You have a turntable lock with four round dials. Each dial wheel has 10 numbers: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'. Each dial wheel can rotate freely: for example, change '9' to '0', and '0' to '9'. Each rotation can only rotate one digit of one dial wheel.

The initial number of the lock is' 0000 ', a string representing the number of four dials.

The list deands contains a set of death numbers. Once the number of the dial wheel is the same as any element in the list, the lock will be permanently locked and can no longer be rotated.

The string target represents the number that can be unlocked. You need to give the minimum number of rotations required for unlocking. If it cannot be unlocked anyway, return - 1.

Input: deadends = ["0201","0101","0102","1212","2002"], target = "0202"
Output: 6
 Explanation:
Possible movement sequences are "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202". 
be careful "0000" -> "0001" -> "0002" -> "0102" -> "0202" Such a sequence cannot be unlocked,
Because when you dial "0102" The lock will be locked.

input: deadends = ["8888"], target = "0009"
Output: 1
 Explanation:
Rotate the last bit in reverse once "0000" -> "0009". 

input: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
Output:-1
 Explanation:
Cannot rotate to target number and is not locked.

input: deadends = ["0000"], target = "8888"
Output:-1

Tips:

  • 1 <= deadends.length <= 500
  • deadends[i].length == 4
  • target.length == 4
  • target is not in deands
  • target and deands [i] consist of only a few digits

analysis

The shortest path problem is usually solved by BFS. Needless to say, BFS is used for sequence traversal of a tree. Here, the initial state 0000 can be used as the root node of the tree, and the next possible state of 0000 is its child node, which is equivalent to sequence traversal of a tree. Each time you traverse a layer, the number of rotations is increased by 1. When you find the target given by the topic, the layer is the minimum number of rotations.

If there is a large amount of data, there may be tens of thousands of nodes on the first floor. The general BFS solution may explode in space. This situation can be solved by using two-way BFS. Search from two directions at the same time. Once the same value is searched, it means that the shortest path connecting the starting point and the ending point is found.

The basic implementation idea of "two-way BFS" is as follows:

  • Create "two queues" for searching in two directions respectively;
  • Create "two hash tables" for "solving duplicate searches on the same node" and "record conversion times";
  • In order to make the two search directions "average" as much as possible, judge which queue has less capacity each time you take a value from the queue for expansion;
  • If the node searched by the other party is found during the search, the shortest path is found.

The pseudo code corresponding to the basic idea of "two-way BFS" is roughly as follows:

d1,d2 Queue in both directions
m1,m2 For a hash table in two directions, record the distance between each node and the starting point
    
// Only when both queues are not empty, it is necessary to continue searching
// If one of the queues is empty, it means that the target node in that direction cannot be searched from one direction to the end
while(!d1.isEmpty() && !d2.isEmpty()) {
    if (d1.size() < d2.size()) {
        update(d1, m1, m2);
    } else {
        update(d2, m2, m1);
    }
}

// update is the logic of taking an element from queue d for "one complete expansion"
void update(Deque d, Map cur, Map other) {}

reference resources: https://leetcode-cn.com/problems/open-the-lock/solution/gong-shui-san-xie-yi-ti-shuang-jie-shuan-wyr9/

code

General BFS:

class Solution {
    // Turn the rotary table upward once
    private String upOne(String str, int index) {
        char[] chs = str.toCharArray();
        if (chs[index] == '9') {
            chs[index] = '0';
        } else {
            chs[index] += 1;
        }
        
        return new String(chs);
    }

    // Turn the rotary table down once
    private String downOne(String str, int index) {
        char[] chs = str.toCharArray();
        if (chs[index] == '0') {
            chs[index] = '9';
        } else {
            chs[index] -= 1;
        }
        
        return new String(chs);
    }

    public int openLock(String[] deadends, String target) {
        // Death number
        Set<String> deadLock = new HashSet<>();
        // Number that has been turned
        Set<String> visited = new HashSet<>();
        for (String str : deadends) {
            deadLock.add(str);
        }

        Queue<String> res = new LinkedList<>();
        res.offer("0000");
        visited.add("0000");
        int step = 0;

        while (!res.isEmpty()) {
            int len = res.size();

            // All nodes in the current queue spread around
            for (int i = 0; i < len; i++) {
                String cur = res.poll();
                // If it is a death number, exit this cycle
                if (deadLock.contains(cur)) {
                    continue;
                }

                // Go to target and directly return to step, which is the minimum number of rotations
                if (cur.equals(target)) {
                    return step;
                }

                // For the current node, each paddle wheel rotates up and down
                for (int j = 0; j < 4; j++) {
                    String next = upOne(cur, j);
                    if (!visited.contains(next)) {
                        res.offer(next);
                        visited.add(next);
                    }

                    next = downOne(cur, j);
                    if (!visited.contains(next)) {
                        res.offer(next);
                        visited.add(next);
                    }
                }
            }

            // Increase steps
            step++;
        }

        // If the target is not found after exhaustive enumeration, it means that it cannot be unlocked
        return -1;
    }
}

Bidirectional BFS:

class Solution {
    String t, 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);
        if (set.contains(s)) return -1;
        int ans = bfs();
        return ans;
    }
    int bfs() {
        // d1 stands for searching from the starting point s (forward)
        // d2 stands for searching from the end t (reverse)
        Deque<String> d1 = new ArrayDeque<>(), d2 = new ArrayDeque<>();
        /*
         * m1 And m2 respectively record how many times the states in the two directions are converted
         * e.g.
         * m1 = {"1000":1} Represents "1000", which is derived from the rotation of s="0000" once
         * m2 = {"9999":3} Represents "9999", which is rotated 3 times by t="9996"
         */
        Map<String, Integer> m1 = new HashMap<>(), m2 = new HashMap<>();
        d1.addLast(s);
        m1.put(s, 0);
        d2.addLast(t);
        m2.put(t, 0);

        /*
         * Only when both queues are not empty, it is necessary to continue searching
         * If one of the queues is empty, it means that the target node in a certain direction cannot be found
         * e.g. 
         * For example, if d1 is empty, it means that the search from s can not find t, and the reverse search is unnecessary
         */
        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);
        // Enumerate which character to replace
        for (int i = 0; i < 4; i++) {
            // It can be "forward rotation" or "reverse rotation". Here, directly enumerate the offset [- 1,1], and then skip 0
            for (int j = -1; j <= 1; j++) {
                if (j == 0) continue;

                // Get the replacement string str
                int origin = pcs[i] - '0';
                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 it is found in the "other direction", it indicates that the shortest path has been found, otherwise join the queue
                if (other.containsKey(str)) { 
                    return step + 1 + other.get(str);
                } else {
                    deque.addLast(str);
                    cur.put(str, step + 1);
                }
            }
        }
        return -1;
    }
}

Added by natalieG on Wed, 26 Jan 2022 02:51:52 +0200