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”
输出: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. 每个 deadendstarget 中的字符串的数字会在 10,000 个可能的情况 '0000''9999' 中产生。

解题思路

BFS
一个密码,只转一下,总共有4个位置,每个位置可以向上转,也可以向下转,一共有8种可能。
比如说从"0000"开始,转一次,可以穷举出"1000", "9000", "0100", "0900"...共 8 种密码。然后,再以这 8 种密码作为基础,对每个密码再转一下,穷举出所有可能…
仔细想想,这就可以抽象成一幅图,每个节点有 8 个相邻的节点,又让你求最短距离,这不就是典型的 BFS 嘛,BFS的框架就可以派上用场了。
还有如下问题需要解决:
1、会走回头路。比如说我们从"0000"拨到"1000",但是等从队列拿出"1000"时,还会拨出一个"0000",这样的话会产生死循环,所以应该有一个集合记录已经访问过的密码。
2、终止条件,按照题目要求,我们找到target就应该结束并返回拨动的次数。
3、对deadends的处理,按道理这些「死亡密码」是不能出现的,也就是说你遇到这些密码的时候需要跳过。
其他:
C++ vector查找是否访问过当前密码和是否在死亡队列里会超时,换成底层使用hash实现的unordered_set,可以大大加速查询

  1. class Solution {
  2. public:
  3. int openLock(vector<string>& deadends, string target) {
  4. unordered_set<string> visited;
  5. unordered_set<string> deads(deadends.begin(),deadends.end());//vector转hash,加速查询
  6. queue<string> que;
  7. int step = 0;
  8. que.push("0000");
  9. visited.insert("0000");
  10. int queSize;
  11. string cur;
  12. while (!que.empty()) {
  13. queSize = que.size();
  14. for (int i = 0;i < queSize;i++) {
  15. cur = que.front();
  16. que.pop();
  17. if (deads.count(cur)) continue; //当前密码包含在死亡队列里,跳过这个选择
  18. //到达目标了,直接返回
  19. if (cur == target) return step;
  20. //四个拨轮都轮着拨一遍
  21. for (int j = 0;j < 4;j++) {
  22. string output1 = upMove(cur,j);
  23. if ( !visited.count(output1) ) {
  24. visited.insert(output1);
  25. que.push(output1);
  26. }
  27. string output2 = downMove(cur,j);
  28. if ( !visited.count(output2) ) {
  29. visited.insert(output2);
  30. que.push(output2);
  31. }
  32. }
  33. }
  34. step++;
  35. }
  36. return -1;
  37. }
  38. string upMove(string cur,int pos) {//向上拨
  39. cur[pos] = (cur[pos] == '9')? '0':cur[pos] + 1;
  40. return cur;
  41. }
  42. string downMove(string cur,int pos) {//向下拨
  43. cur[pos] = (cur[pos] == '0')? '9':cur[pos] - 1;
  44. return cur;
  45. }
  46. };