(BFS)752.打开转盘锁 | 773.滑动谜题 | 854.相似度为K的字符串

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;
    }
};

854. 相似度为 K 的字符串

对于某些非负整数 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;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值