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
解释:无法旋转到目标数字且不被锁定。
思路:
BFS(在一幅「图」中找到从起点 start
到终点 target
的最近距离)
class Solution {
public:
int openLock(vector<string>& deadends, string target) {
int step=0;
unordered_set<string> deadSet;// 记录需要跳过的死亡数字
unordered_set<string> visited;//记录已经穷举过的密码,防止走回头路
for(string s:deadends)
{
deadSet.insert(s);
}
queue<string> q;
q.push("0000");
visited.insert("0000");
while(!q.empty())
{
int size=q.size();
/* 将当前队列中的所有节点向周围扩散 */
for(int i=0;i<size;i++)
{
string cur=q.front();
q.pop();
//如果是死亡数字,就跳过这种情况
if(deadSet.count(cur))
{
continue;
}
//如果等于target,就返回操作步数
if(!cur.compare(target))
{
return step;
}
/* 将一个节点cur的未遍历相邻节点加入队列 */
/*相邻结点有八个,四个位置,每个位置有拨上拨下两种选择*/
for(int j=0;j<4;j++)
{
//朝上拨
string s1=cur;
if(s1[j]=='9')
s1[j]='0';
else
s1[j]+=1;
if(!visited.count(s1))//未遍历过,就加入队列
{
q.push(s1);
visited.insert(s1);
}
//朝下拨
string s2=cur;
if(s2[j]=='0')
s2[j]='9';
else
s2[j]-=1;
if(!visited.count(s2))//未遍历过,就加入队列
{
q.push(s2);
visited.insert(s2);
}
}
}
/* 在这里增加步数 */
step++;
}
//如果穷举完都没找到目标密码,那就是找不到了
return -1;
}
};
773.滑动谜题
在一个 2 x 3 的板上(board)有 5 块砖瓦,用数字 1~5 来表示, 以及一块空缺用 0 来表示。一次 移动 定义为选择 0 与一个相邻的数字(上下左右)进行交换.
最终当板 board 的结果是 [[1,2,3],[4,5,0]] 谜板被解开。
给出一个谜板的初始状态 board ,返回最少可以通过多少次移动解开谜板,如果不能解开谜板,则返回 -1 。
示例 1:
输入:board = [[1,2,3],[4,0,5]]
输出:1
解释:交换 0 和 5 ,1 步完成
思路:BFS
如何用 BFS 算法秒杀各种智力题 :: labuladong的算法小抄 (gitee.io)
用字符串来描述board当前的状态,要把board从start状态以最小的步数转换成target=“123450”状态。 从状态cur向外扩散,即是‘ 0 ’与它周围的板块进行交换,我们通过neighbors数组来描述相邻关系。
vector<vector<int>> neighbors={{1,3},{0,2,4},{1,5},{0,4},{1,3,5},{2,4}};
class Solution {
public:
void swap(string& s,int i,int j)
{
char temp;
temp=s[i];
s[i]=s[j];
s[j]=temp;
}
int slidingPuzzle(vector<vector<int>>& board) {
string start;
start.resize(6);
for(int i=0;i<2;i++)//构建初始状态start
{
for(int j=0;j<3;j++)
{
start[i*3+j]=board[i][j]+'0';//注意类型转换,start中存的是字符,board中存的是数字
}
}
//用来描述相邻关系的neighbors数组
vector<vector<int>> neighbors={{1,3},{0,2,4},{1,5},{0,4},{1,3,5},{2,4}};
int step=0;
string target="123450";//目标状态
queue<string> q;
unordered_set<string> visited;//防止走回头路,陷入死循环
q.push(start);
visited.insert(start);
while(!q.empty())
{
int size=q.size();
for(int i=0;i<size;i++)
{
string cur=q.front();
if(!cur.compare(target))
{
return step;
}
int index=cur.find('0');//找到cur中'0'对应的下标
q.pop();
//从和'0'相邻的字符开始扩展(和'0'进行交换)
for(int neighbor:neighbors[index])
{
string tmp=cur;
swap(tmp,neighbor,index);
//防止走回头路
if(!visited.count(tmp))
{
q.push(tmp);
visited.insert(tmp);
}
}
}
step++;
}
return -1;
}
};
对于某些非负整数 k
,如果交换 s1
中两个字母的位置恰好 k
次,能够使结果字符串等于 s2
,则认为字符串 s1
和 s2
的 相似度为 k
。
给你两个字母异位词 s1
和 s2
,返回 s1
和 s2
的相似度 k
的最小值。
示例 1:
输入:s1 = "ab", s2 = "ba" 输出:1
示例 2:
输入:s1 = "abc", s2 = "bca" 输出:2
思路:BFS,求最小步数
从当前位置往后面找该位置正确的字母,交换,直至完全等于s2。
这里要注意剪枝逻辑:
一、如果交换完后的情况在之前出现过,再次出现的话就不用继续跑了(因为1、如果都是本层的情况,那么跑一种就够了 2、如果是上层的情况,那么现在这种肯定比上次那种步数多,也不用再跑了)。( unordered_set < string > visited )
二、判断要交换的那个字母,现在所在位置是否已经是正确的,如果本来就是正确的就不用换过来了。(s [ j ] ! = s2 [ j ] )
//求最小值,所以使用BFS算法
class Solution {
public:
int kSimilarity(string s1, string s2) {
queue<pair<string,int>> q;
unordered_set<string> visited;//防止重复搜索字符串
q.emplace(s1,0);
int step=0;
while(!q.empty())
{
int size=q.size();
for(int i=0;i<size;i++)
{
pair<string,int> cur=q.front();
int index=cur.second;
string s=cur.first;
if(s==s2)
return step;
q.pop();
while(index<s.size()&&s[index]==s2[index])
{
index++;
}
//选择列表————s[index..]中的任意一个字符
for(int j=index;j<s.size();j++)
{
//剪枝,选择s[j]!=s2[j]的字符
if(s[j]==s2[index]&&s[j]!=s2[j])
{
swap(s[j],s[index]);
if(!visited.count(s))//如果交换后的结果之前没有出现过
{
visited.insert(s);//记录到备忘录里
q.emplace(s,index+1);//加入队列
}
//再交换回来,保持for循环中s不变,因为for循环中的选项都是由s引出的
swap(s[j],s[index]);
}
}
}
//操作步数加一
step++;
}
return 0;
}
};