bfs从初始值逐步变化到目标值的最短路径

单词演变

在字典(单词列表) wordList 中,从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列:

序列中第一个单词是 beginWord 。
序列中最后一个单词是 endWord 。
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典 wordList 中的单词。
给定两个长度相同但内容不同的单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。

  1. 从beginWord开始bfs每层res++,bfs本身就是寻找最短路的过程
class Solution {
public:
    void getNextWolds(unordered_set<string>& visited,string& currentWord,queue<string>& q1){
        for(int i=0;i!=currentWord.size();i++){
            // 挨个换字母匹配 回溯的思想
            char temp = currentWord[i]; 
            for(char ch='a';ch<='z';ch++){
                currentWord[i] = ch;
                if(ch !=temp && visited.count(currentWord)){
                    q1.emplace(currentWord);
                }
            }
            currentWord[i] = temp;
        }
    }
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        unordered_set<string> visited;
        // 建表
        for(auto word:wordList){
            visited.emplace(word);
        }
        // 查endWord
        if(!visited.count(endWord)){
            return 0;
        }
        queue<string> q1;
        q1.emplace(beginWord);
        int res = 0;
        while(!q1.empty()){
            // bfs
            int size = q1.size();
            res++;
            while(size--){
                string currentWord = q1.front();
                q1.pop();
                visited.erase(currentWord); //删除元素防止重复转换,bfs本身就是求最小路径的过程
                if(currentWord == endWord){
                    return res;
                }
                getNextWolds(visited,currentWord,q1);
            }
        }
        return 0;
    }
};

改进:

  1. 因为是单一起点、单一目标节点的最短距离问题。可以采用双向bfs。一棵树的增长比较慢因为每一层的都越来越大,但如果两棵树同时扩展就会快一点。
  2. 每次遍历层size比较小的那数,如果在另一颗树找到了,就说明找打了最短路
class Solution {
public:
    bool getNeighbor(unordered_set<string>& visited, unordered_set<string>& st1, unordered_set<string>& st2, unordered_set<string>& st3, string& word) {
        for (int i = 0; i < word.size(); ++i) {
            char temp = word[i];
            for (char ch = 'a'; ch <= 'z'; ++ch) {
                word[i] = ch;
                if (ch != temp && visited.count(word)) {
                    if (st2.count(word)) {
                        return true; // 在另一个bfs层中找到相同的单词,说明已经串成一条路
                    }
                    st3.insert(word);
                }
            }
            word[i] = temp;
        }
        return false;
    }
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        unordered_set<string> visited;
        // 建表
        for(auto word:wordList){
            visited.emplace(word);
        }
        // 查endWord
        if(!visited.count(endWord)){
            return 0;
        }
        unordered_set<string> st1;
        unordered_set<string> st2;
        st1.emplace(beginWord);
        st2.emplace(endWord);
        int res = 2;
        while(!st1.empty() && !st2.empty()){
            // 每次bfs较小的
            if(st1.size()>st2.size()){
                swap(st1,st2);
            }
            // bfs
            unordered_set<string> st3;
            for(auto it=st1.begin(); it!=st1.end(); it++){
                string currentWord = *it;
                visited.erase(currentWord); //删除元素防止重复转换,bfs本身就是求最小路径的过程

                if (getNeighbor(visited, st1, st2, st3, currentWord)) {
                    return res;
                }
            }
            // 修改层
            st1 = st3;
            res++;
        }
        return 0;
    }
};

开密码锁

一个密码锁由 4 个环形拨轮组成,每个拨轮都有 10 个数字: ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’ 。每个拨轮可以自由旋转:例如把 ‘9’ 变为 ‘0’,‘0’ 变为 ‘9’ 。每次旋转都只能旋转一个拨轮的一位数字。
锁的初始数字为 ‘0000’ ,一个代表四个拨轮的数字的字符串。
列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。
字符串 target 代表可以解锁的数字,请给出解锁需要的最小旋转次数,如果无论如何不能解锁,返回 -1 。

  1. bfs 每次转一个,加了死亡数组和不走回头路的条件
  2. bfs套路都差不多,就是写的时候小心点
  3. 回溯思想有continu和break的别忘了恢复初值,bug找了半天
class Solution {
public:
    int openLock(vector<string>& deadends, string target) {
        unordered_set<string> st_deadends(deadends.begin(),deadends.end());

        queue<string> q1;
        string start = "0000";
        if(st_deadends.count(start)){
            return -1;
        }else if(start == target){
            return 0;
        }
        q1.emplace(start);
        st_deadends.emplace(start);
        int res = 0;
        while(!q1.empty()){
            int size = q1.size();
            res++;
            while(size--){
                string currentNum = q1.front();
                q1.pop();
                // 转动一次的所有情况
                //cout<<"开始转动一次"<<currentNum<<endl;
                for(int i=0;i!=4;++i){
                    //cout<<"选择转动的是第"<<i<<"个数"<<endl;
                    char temp = currentNum[i];
                    for(int j=-1;j<=1;j+=2){
                        currentNum[i] = (temp-'0'+j+10)%10+'0';
                       // cout<<"修改后的值是"<<currentNum<<endl;
                        if(st_deadends.count(currentNum)){      
                            //cout<<"不满足条件"<<currentNum<<endl;
                            currentNum[i] = temp;// 回溯思想有continu和break的别忘了恢复初值,bug找了半天
                            continue;
                        }
                        if(currentNum == target){
                            //cout<<"得到结果"<<currentNum<<endl;
                            return res;
                        }
                        q1.emplace(currentNum);
                        //cout<<"添加这个数"<<currentNum<<endl;
                        st_deadends.emplace(currentNum); // 不走回头路
                        currentNum[i] = temp;
                        //cout<<"还原这个数"<<currentNum<<endl;
                    }
                }
            }
        }
        return -1;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值