转盘锁
问题描述
你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有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" 时这个锁就会被锁定。
示例二:
输入: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
输出:-1
解释:
无法旋转到目标数字且不被锁定。
编程思路及代码实现
要制定合理的搜索策略:对10000个情况进行分类:
- 使用带有哈希集的广度优先搜索
- 每一次每个拨轮+1或者-1
- 添加的新数不能在先前出现过且不在死亡序列中
- 注意“0000”在死亡序列中的特殊情况
代码实现:
class Solution {
public:
int openLock(vector<string>& deadends, string target) {
queue<string> Myqueue; // 用于存储节点的队列
unordered_set<string> used; // 存储已经遍历过的节点
unordered_set<string> deadlocks(deadends.begin(),deadends.end()); // 存储所有的死亡序列
int step{0}; // 初始化步数计数器
vector<int> directs{-1,1}; // 拨轮+1或者-1
Myqueue.push("0000"); // 将初始结点入队
used.insert("0000");
if (deadlocks.count("0000")) { //针对初始节点 “0000”在死亡序列中的特殊情况进行判断
return -1;
}
string cur;
while(!Myqueue.empty()) { // 广度优先搜索主循环(循环终止条件为队列为空)
int size = Myqueue.size();
for (int i=0 ; i < size ; i++) {
cur=Myqueue.front();
Myqueue.pop();
if (cur==target) {
return step;
}
if (deadlocks.count((cur))) {
continue;
}
for (int j=0 ; j<cur.size() ; j++) {
for (auto direct:directs) {
string change;
change=cur;
change[j]=((cur[j]-'0')+10+direct)%10+'0';
if (used.count(change)||deadlocks.count(change)) {
continue;
}
Myqueue.push(change);
used.insert(change);
}
}
}
step = step + 1;
}
return -1;
}
};
涉及到的C++知识:
C++中的set:
- 概念:set作为一个容器用来存储同一数据类型的数据,并且能从一个数据集合中取出数据,在set中每个元素的值都唯一,而且系统能根据元素的值自动进行排序。应该注意的是set中元素的值不能直接被改变。
unordered_set描述大体如下:无序集合容器(unordered_set)是一个存储唯一(unique,即无重复)的关联容器(Associative container),容器中的元素无特别的秩序关系,该容器允许基于值的快速元素检索,同时也支持正向迭代。 在一个unordered_set内部,元素不会按任何顺序排序,而是通过元素值的hash值将元素分组放置到各个槽(Bucker,也可以译为“桶”),这样就能通过元素值快速访问各个对应的元素(均摊耗时为O(1))。 - 粗略理解:就是一个装东西的容器,但是放进去的东西不能重复,可以通过这个东西的值对其进行检索。
- 一些操作:(这里给的是set的相关操作)在本题中使用了count()。
begin(); // 返回指向第一个元素的迭代器
end(); // 返回指向最后一个元素的迭代器
clear(); // 清除所有元素
count(); // 返回某个值元素的个数
empty(); // 如果集合为空,返回true
equal_range(); //返回集合中与给定值相等的上下限的两个迭代器
erase()–删除集合中的元素
find()–返回一个指向被查找到元素的迭代器
get_allocator()–返回集合的分配器
insert()–在集合中插入元素
lower_bound()–返回指向大于(或等于)某值的第一个元素的迭代器
key_comp()–返回一个用于元素间值比较的函数
max_size()–返回集合能容纳的元素的最大限值
rbegin()–返回指向集合中最后一个元素的反向迭代器
rend()–返回指向集合中第一个元素的反向迭代器
size()–集合中元素的数目
swap()–交换两个集合变量
upper_bound()–返回大于某个值元素的迭代器
value_comp()–返回一个用于比较元素间的值的函数
C++中的queue:
队列是C++中先入先出的数据结构,会用于广度优先搜索,以下是其基本操作:
.push() -入队,在队列后面添加一个成员
.pop() - 出队
.front() - 返回队列中头部成员
.back() - 返回队列中尾部成员
.size() - 返回队列中元素个数
其他用到的操作:
string.size() - 返回字符串中的字符数