LeetCode 752 打开转盘锁题解

LeetCode 752 打开转盘锁题解

[toc]

题目

你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 。每个拨轮可以自由旋转:例如把 '9' 变为 '0','0' 变为 '9' 。每次旋转都只能旋转一个拨轮的一位数字。

锁的初始数字为 '0000' ,一个代表四个拨轮的数字的字符串。

列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。

字符串 target 代表可以解锁的数字,你需要给出最小的旋转次数,如果无论如何不能解锁,返回 -1。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/open-the-lock
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例

示例 1:

输入:deadends = ["0201","0101","0102","1212","2002"], target = "0202"
输出:6
解释:
可能的移动序列为 "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202"。
注意 "0000" -> "0001" -> "0002" -> "0102" -> "0202" 这样的序列是不能解锁的,
因为当拨动到 "0102" 时这个锁就会被锁定。

示例 2:

输入: deadends = ["8888"], target = "0009"
输出:1
解释:
把最后一位反向旋转一次即可 "0000" -> "0009"。

示例 3:

输入: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
输出:-1
解释:
无法旋转到目标数字且不被锁定。

示例 4:

输入: deadends = ["0000"], target = "8888"
输出:-1

提示:

  1. 死亡列表 deadends 的长度范围为 [1, 500]。
  2. 目标数字 target 不会在 deadends 之中。
  3. 每个 deadends 和 target 中的字符串的数字会在 10,000 个可能的情况 '0000' 到 '9999' 中产生。

思路与分析

设N表示状态的位数,题目中用4个数字表示状态,因此N=4。A表示每个数字可以选择的范围的大小,题目中每个数字的选取范围是0-9共10个数字。因此总的状态个数为 A * A * A * A = A ^ N = 10 * 10 * 10 * 10 = 10^4=10000。 因此,整个搜索范围相当于1个包含10000个结点的图。如果仅转动一位数字且转动的幅度为1,得到的结果不包含在“死锁”状态即deadends数组中,则这两个状态(结点)之间有边。

题目要求最小转动次数,相当于最少次改变状态,也就是要求状态转移图上从“0000”到目标状态(结点)的最短路径。这显然可以使用广度优先搜索的算法来进行这个过程。

代码

有了这个思路,代码就不难书写了。以下采用Leetcode给出的模板来实现。更多对于性能的优化可以参考评论区以及其他的题解。

//模板
/**
 * Return the length of the shortest path between root and target node.
 */
int BFS(Node root, Node target) {
    Queue queue;  // store all nodes which are waiting to be processed
    Set used;     // store all the used nodes
    int step = 0;       // number of steps neeeded from root to current node
    // initialize
    add root to queue;
    add root to used;
    // BFS
    while (queue is not empty) {
        step = step + 1;
        // iterate the nodes which are already in the queue
        int size = queue.size();
        for (int i = 0; i < size; ++i) {
            Node cur = the first node in queue;
            return step if cur is target;
            for (Node next : the neighbors of cur) {
                if (next is not in used) {
                    add next to queue;
                    add next to used;
                }
            }
            remove the first node from queue;
        }
    }
    return -1;          // there is no path from root to target
}
class Solution {
    public int openLock(String[] deadends, String target) {
        //使用Hash存储“死锁状态”,以便快速判断某个状态是否存在是“死锁”状态
        Set dead = new HashSet<>();
        for (String deadend : deadends) {
            dead.add(deadend);
        }

        //模板中的队列及使用过的结点的hash集。因为转动锁的时候可能造成重复的状态,因此需要hash集来避免重复访问。
        Queue q = new LinkedList<>();
        Set seen = new HashSet<>();
        q.offer("0000");
        seen.add("0000");
        int step = 0;

        while (!q.isEmpty()) {
            int size = q.size();
            //每个for循环表示1轮转动。
            for (int i = 0; i < size; i++) {
                //取出上一轮存入的结点,用pre表示。
                String pre = q.poll();
                //因为题目中说明目标数字 target 不会在 deadends 之中。“0000”要么不是target,要么不在deadends之中
                //因此直接判断结点是否等于target即可。若当前结点不等于target,则判断当前结点是否在deadens之中,若在之中,
                //则表面当前结点是不可达的,因此无需处理当前结点可能转动的状态。
                if (pre.equals(target)) {
                    return step;
                }else if (!dead.contains(pre)) {
                    //处理转动的状态,共有4位数字需要处理
                    for (int j = 0; j < 4; j++) {
                        //每个数字可能+1或-1
                        for (int k = -1; k <= 1; k += 2) {
                            //用于计算当前位的数字 0-9加一减一后的范围为-1-10,加上10之后为9-20,对10取模后为0-9;
                            int y = ((pre.charAt(j) - '0') + 10 + k) % 10;
                            //生成当前状态
                            String cur = pre.substring(0, j) + ("" + y) + pre.substring(j + 1);
                            if (!seen.contains(cur)) {
                                q.offer(cur);
                                seen.add(cur);
                            }
                        }
                    }
                }
            }
            step += 1;
        }
        return-1;
    }
}

你可能感兴趣的:(LeetCode 752 打开转盘锁题解)