【leetcode】【medium】127. Word Ladder​​​​​​​

230 篇文章 0 订阅

127. Word Ladder

Given two words (beginWord and endWord), and a dictionary's word list, find the length of shortest transformation sequence from beginWord to endWord, such that:

  1. Only one letter can be changed at a time.
  2. Each transformed word must exist in the word list. Note that beginWord is not a transformed word.

Note:

  • Return 0 if there is no such transformation sequence.
  • All words have the same length.
  • All words contain only lowercase alphabetic characters.
  • You may assume no duplicates in the word list.
  • You may assume beginWord and endWord are non-empty and are not the same.

Example 1:

Input:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]

Output: 5

Explanation: As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
return its length 5.

Example 2:

Input:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]

Output: 0

Explanation: The endWord "cog" is not in wordList, therefore no possible transformation.

 题目链接:https://leetcode-cn.com/problems/word-ladder/

 

思路

做过perfect square这道题,就知道本题可以采用类似思路,转为图问题找最短路径。

但本题搜索空间扩大了,连通条件也边复杂了,需要多一些时间复杂度上的思考。

法一:BFS

遍历逻辑:每从队列取出一个单词,在单词表中找到相差1个字母的未访问单词加入队列。

注意:不能一边遍历,一边把已访问的元素从表中删除。这会导致一旦中间的元素被删,后面就会出现有元素遍历不到。因此要有变量记录元素是否遍历过。

问题:当单词表较大时,代码会超时。

class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        if(wordList.size()<=0 || beginWord==endWord) return 0;
        queue< pair<int, int>> record;
        record.push(make_pair(-1, 1));
        bool visit[wordList.size()] = {false};
        while(!record.empty()){
            auto tmp = record.front();
            record.pop();
            string s1;
            if(tmp.first==-1){
                s1 = beginWord;
            }else{
                s1 = wordList[tmp.first];
            }
            if(s1==endWord) return tmp.second;
            for(int i=0; i<wordList.size(); ++i){
                string s2;
                s2 = wordList[i];
                if(!visit[i] && isConnect(s1, s2)){
                    record.push(make_pair(i, tmp.second+1));
                    visit[i] = true;
                }
            }
        }
        return 0;
    }
    bool isConnect(string s1, string s2){
        int dif = 0;
        for(int i=0; i<s1.size(); ++i){
            if(dif>1) break;
            if(s1[i]!=s2[i]) ++dif;
        }
        if(dif>1) return false;
        else return true;
    }
};

 

改进:当判断连通时,不去遍历vector,而另一种遍历逻辑:先构造,再去差有没有;同时把遍历过的元素直接从列表中删除。

问题:由于vector查找慢,依旧超时。

class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        if(wordList.size()<=0 || beginWord==endWord) return 0;
        queue< pair<string, int>> record;
        record.push(make_pair(beginWord, 1));
        while(!record.empty()){
            auto tmp = record.front();
            record.pop();
            string s1 = tmp.first;
            if(s1==endWord) return tmp.second;
            for(int i=0; i<s1.size(); ++i){
                string s2 = s1;
                for(char c='a'; c<='z'; ++c){
                    if(c==s2[i]) continue;
                    s2[i] = c;
                    auto it = find(wordList.begin(), wordList.end(), s2);
                    if(it!=wordList.end()){
                        record.push(make_pair(s2, tmp.second+1));
                        wordList.erase(it);
                    }
                }
            }
        }
        return 0;
    }
};

 

改进:将vector装进unordered_set加快查找速度。 

现在可以通过。

class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        if(wordList.size()<=0 || beginWord==endWord) return 0;
        unordered_set<string> list(wordList.begin(), wordList.end());
        queue< pair<string, int>> record;
        record.push(make_pair(beginWord, 1));
        while(!record.empty()){
            auto tmp = record.front();
            record.pop();
            string s1 = tmp.first;
            if(s1==endWord) return tmp.second;
            for(int i=0; i<s1.size(); ++i){
                string s2 = s1;
                for(char c='a'; c<='z'; ++c){
                    if(c==s2[i]) continue;
                    s2[i] = c;
                    auto it = list.find(s2);
                    if(it!=list.end()){
                        record.push(make_pair(s2, tmp.second+1));
                        list.erase(it);
                    }
                }
            }
        }
        return 0;
    }
};

 

法二:双向BFS

看分享学会的。

提出的思路是:当从一个点出发走向另一个点时,每多走一步,可能的路径条数就会不断增多(搜索空间扩大)——就是一棵数从根节点不断长出叶子节点。而如果同时从目标节点出发,两边同时向中间走,则可以缩小搜索空间。

!注意编程的坑:beginWord和endWord在初始时的visit值也要记录!!亲测不计不给过特殊样例。。

代码一:找连通节点时对单词表逐个遍历

逻辑查找费时。

class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        if(wordList.size()<=0 || beginWord==endWord
           || find(wordList.begin(), wordList.end(), endWord)==wordList.end()) return 0;
        queue<string> record1, record2;
        record1.push(beginWord);
        record2.push(endWord);
        unordered_map<string, int> visit;
        visit[beginWord] = 1;
        visit[endWord] = 2;
        int step = 0;
        while(!record1.empty() && !record2.empty()){
            ++step;
            int flag = (record1.size()<=record2.size())?1:2;
            queue<string> &record = (flag==1)?record1:record2;
            int len = record.size();
            for(int i=0; i<len; ++i){
                auto s1 = record.front();
                record.pop();
                if(visit[s1]==3) return step;
                for(int j=0; j<wordList.size(); ++j){
                    string s2 = wordList[j];
                    if(visit[s2]!=flag && isConnect(s1, s2)){
                        record.push(s2);
                        visit[s2] = (visit[s2]==0)?flag:3;
                    }
                }
            }
        }
        return 0;
    }
    bool isConnect(string s1, string s2){
        int dif = 0;
        for(int i=0; i<s1.size(); ++i){
            if(dif>1) break;
            if(s1[i]!=s2[i]) ++dif;
        }
        if(dif==1){
            return true;
        }
        else return false;
    }
};

 

代码二:找连通节点时先构造再查找

点1:在visit创建时将单词表初始化,用于构造单词后的查找,提升查找速度。

点2:不能像单向BFS那样访问一个元素就把他从单词表删除,因为现在元素有3种状态:未访问/正向访问/逆向访问。

在数据较多的情况下运行时间大大减少。

class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        if(wordList.size()<=0 || beginWord==endWord
           || find(wordList.begin(), wordList.end(), endWord)==wordList.end()) return 0;
        queue<string> record1, record2;
        record1.push(beginWord);
        record2.push(endWord);
        unordered_map<string, int> visit;
        for(const auto &word: wordList)
            visit[word]=0;
        visit[beginWord] = 1;
        visit[endWord] = 2;
        int step = 0;
        while(!record1.empty() && !record2.empty()){
            ++step;
            int flag = (record1.size()<=record2.size())?1:2;
            queue<string> &record = (flag==1)?record1:record2;
            int len = record.size();
            for(int i=0; i<len; ++i){
                auto s1 = record.front();
                record.pop();
                if(visit[s1]==3) return step;
                for(int j=0; j<s1.size(); ++j){
                    for(char c='a'; c<='z'; ++c){
                        string s2 = s1;
                        if(s2[j]==c) continue;
                        s2[j] = c;
                        if(visit.count(s2)>0 && visit[s2]!=flag){
                            record.push(s2);
                            visit[s2] = (visit[s2]==0)?flag:3;
                        }
                    }
                }
            }
        }
        return 0;
    }
};

 

参考

1.双向BFS:https://leetcode-cn.com/problems/word-ladder/solution/shuang-xiang-bfs-by-joy-teng/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值