打开转盘锁
你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: ‘0', ‘1', ‘2', ‘3', ‘4', ‘5', ‘6', ‘7', ‘8', ‘9' 。每个拨轮可以自由旋转:例如把 ‘9' 变为 '0','0' 变为 ‘9' 。每次旋转都只能旋转一个拨轮的一位数字。
锁的初始数字为 ‘0000' ,一个代表四个拨轮的数字的字符串。
列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。
字符串 target 代表可以解锁的数字,你需要给出解锁需要的最小旋转次数,如果无论如何不能解锁,返回 -1 。
示例:
输入:deadends = ["0201","0101","0102","1212","2002"], target = "0202"
输出:6
解释:
可能的移动序列为 "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202"。
注意 "0000" -> "0001" -> "0002" -> "0102" -> "0202" 这样的序列是不能解锁的,
因为当拨动到 "0102" 时这个锁就会被锁定。
思路
解法一:
本题是「最短路/最小步数」问题,针对此类问题,通常使用广度优先搜索(BFS)。
「双向 BFS」可以同时从两个方向开始搜索,一旦搜索到相同的值,意味着找到了一条联通起点和终点的最短路径。
「双向 BFS」的基本实现思路如下:
- 创建「两个队列」分别用于两个方向的搜索;
- 创建「两个哈希表」用于「解决相同节点重复搜索」和「记录转换次数」;
- 为了尽可能让两个搜索方向"平均",每次从队列中取值进行扩展时,先判断哪个队列容量较少;
- 如果在搜索过程中「搜索到对方搜索过的节点」,说明找到了最短路径。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Solution
{
public:
string s, t;
unordered_set<string> st;
int openLock(vector<string> &deadends, string target)
{
s = "0000";
t = target;
if (s == t)
{
return 0; // 初始时满足条件
}
for (const auto &d : deadends)
{
st.insert(d);
}
if (st.count(s))
{
return -1;
}
int ans = bfs();
return ans;
}
// bfs逻辑
int bfs()
{
queue<string> q1, q2; // 双向队列
unordered_map<string, int> m1, m2; // 统计次数
q1.push(s);
m1[s] = 0;
q2.push(t);
m2[t] = 0;
// while (q1.size() != 0 && q2.size() != 0)
while (q1.size() and q2.size())
{ // 若 q1 和 q2 都为空还搜索不到,则证明无法走通
int t = -1;
if (q1.size() <= q2.size())
{
t = update(q1, m1, m2); // 从队列中取出一个元素进行判断
}
else
{
t = update(q2, m2, m1);
}
if (t != -1)
{
return t;
}
}
return -1;
}
int update(queue<string> &q, unordered_map<string, int> &cur, unordered_map<string, int> &other)
{
int m = q.size();
while (m-- > 0)
{
string t = q.front(); // 队列中去除一个四位数
q.pop();
int step = cur[t];
for (int i = 0; i < 4; i++)
{ // 四位数
for (int j = -1; j <= 1; j++)
{ // 三种可能,+1, 0, -1
if (j == 0)
{
continue;
}
int origin = t[i] - '0';
int next = (origin + j) % 10;
if (next == -1)
{ // 若为0,j= -1,则应为9
next = 9;
}
string copy = t;
copy[i] = '0' + next; // 转化为字符
if (st.count(copy) or cur.count(copy))
{ // 不可取或之前走过
continue;
}
if (other.count(copy))
{ //两个队列搜索到同样的数,证明可以走通
return step + 1 + other[copy];
}
else
{
q.push(copy);
cur[copy] = step + 1;
}
}
}
}
return -1;
}
};