leetcode:752. 打开转盘锁

题目

你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 。每个拨轮可以自由旋转:例如把 '9' 变为 '0''0' 变为 '9' 。每次旋转都只能旋转一个拨轮的一位数字。
锁的初始数字为 '0000' ,一个代表四个拨轮的数字的字符串。
列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。
字符串 target 代表可以解锁的数字,你需要给出解锁需要的最小旋转次数,如果无论如何不能解锁,返回 -1

示例:

  1. 输入:deadends = ["0201","0101","0102","1212","2002"], target = "0202"
  2. 输出:6
  3. 解释:
  4. 可能的移动序列为 "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202"
  5. 注意 "0000" -> "0001" -> "0002" -> "0102" -> "0202" 这样的序列是不能解锁的,
  6. 因为当拨动到 "0102" 时这个锁就会被锁定。
输入: deadends = ["8888"], target = "0009"
输出:1
解释:把最后一位反向旋转一次即可 "0000" -> "0009"。
输入: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
输出:-1
解释:无法旋转到目标数字且不被锁定。

解答 & 代码

BFS 广度优先搜索(层序遍历)

本题求转盘锁从初始 “0000” 拨出 target 的最少旋转次数,就相当于求图中从起点走到终点的最短路径,就可以用 BFS 解决(更准确的说是层序遍历),广度优先遍历第一次走到 target 的层数就是最短步数

class Solution {
public:
    int openLock(vector<string>& deadends, string target) {
        // 哈希表,存储死亡密码
        unordered_set<string> deadMap;
        for(int i = 0; i < deadends.size(); ++i)
            deadMap.insert(deadends[i]);

        string cur = "0000";    // 初始密码
        // 如果初始密码 or 目标密码是死亡密码,则直接返回 -1
        if(deadMap.find(cur) != deadMap.end() || deadMap.find(target) != deadMap.end())
            return -1;

        // BFS 广度优先遍历(层序遍历)
        queue<string> q;        // 队列
        q.push(cur);
        unordered_set<string> visitedMap;    // 哈希表,记录已穷举过的密码,防止走回头路
        visitedMap.insert(cur);
        int step = 0;            // 步数(层数)
        while(!q.empty())
        {
            int levelCnt = q.size();
            // 遍历这一层的所有密码
            for(int i = 0; i < levelCnt; ++i)
            {
                cur = q.front();
                q.pop();
                // 如果当前密码就是目标密码,则直接返回步数
                if(cur == target)
                    return step;
                // 访问所有相邻密码(4 个位置的数字分别加一减一,共 8 种可能)
                for(int j = 0; j < cur.size(); ++j)
                {
                    string next1 = cur;
                    next1[j] = cur[j] == '0' ? '9' : cur[j] - 1;    // 加一
                    if(deadMap.find(next1) == deadMap.end() && visitedMap.find(next1) == visitedMap.end())
                    {
                        visitedMap.insert(next1);
                        q.push(next1);
                    }

                    string next2 = cur;
                    next2[j] = cur[j] == '9' ? '0' : cur[j] + 1;    // 减一
                    if(deadMap.find(next2) == deadMap.end() && visitedMap.find(next2) == visitedMap.end())
                    {
                        visitedMap.insert(next2);
                        q.push(next2);
                    }
                }
            }
            ++step;                // 增加步数(层数)
        }
        return -1;
    }
};

复杂度分析:

  • 时间复杂度[中等] 752. 打开转盘锁 - 图1:4 位转盘锁,每位有 10 种数字,因此 4 位转盘数字的可能性共[中等] 752. 打开转盘锁 - 图2种。对于每种可能,需要 O(4) 的时间复杂度遍历每一位去转动,对于转动后的数字,需要 O(4) 的时间复杂度存入队列
  • 空间复杂度[中等] 752. 打开转盘锁 - 图3:队列最多存储共[中等] 752. 打开转盘锁 - 图4个长尾 4 的字符串

执行结果:

执行结果:通过

执行用时:156 ms, 在所有 C++ 提交中击败了 61.82% 的用户
内存消耗:33.8 MB, 在所有 C++ 提交中击败了 65.41% 的用户