LeetCode Week16: Word Ladder II

这里接着上一周完成的题目,写的是Word Ladder II,同样还是一道BFS的题。

题目

Given two words (beginWord and endWord), and a dictionary’s word list, find all shortest transformation sequence(s) from beginWord to endWord, such that:

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

Given:

beginWord = “hit”
endWord = “cog”
wordList = [“hot”,”dot”,”dog”,”lot”,”log”,”cog”]
Return [
[“hit”,”hot”,”dot”,”dog”,”cog”],
[“hit”,”hot”,”lot”,”log”,”cog”]
]

Note:
Return an empty list 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.

我的分析

上一周的Word Ladder题目中,我使用的方法是,通过变换一个字符串的一位字符,判断新字符串是否存在与字典中而得到与原字符串相邻的结点,继而可以通过相邻的结点,一直遍历下去,直到遍历到endWord。

在上周的题解中,我们每次从字典中找到一个新的字符串存在时,都会立即在字典中把这个字符串删除,这是因为题目只要求求出最短路径,并不要求要所有的解,这样的处理方式显然就不适合与Word Ladder II的情况。例如,假设现在beginWord = “hit”, endWord = “cog”,而字典内容为{“hot”,”dot”,”dog”,”lot”,”log”,”cog”},当我们遍历出”cog”是”dog”的领结结点并且将”cog”删除的话,那么就没有办法遍历出”cog”也是”log”的子结点,那么另一条路径就没有办法得到了。这也说明了我们并不能一遍历到就直接将字典中的元素删除,而是应该等同层的结点都遍历结束后,再将字典中的对应字符串删除

那么我现在要借鉴Word Ladder的分析,来解析如何求解Word Ladder II。

1、如何构造路径图
这里写图片描述
如图所示,我们将beginWord当作是初始结点,endWord是终点结点,我们的目的,是建立一条两个结点间的最短路径,这与Word Ladder一致,结合上述的分析,我们需要修改的是,不要马上删除字典中的对应元素而是在同层结点都遍历结束后再删除。这样的话,考虑到一种情况,当两个同层结点会指向同一个子结点时,子结点虽然是遍历到两次,但应该只保留一次,考虑到不重复字符串的存在,我们利用两个set变量cur和next,cur中存放的是当前结点的字符串,而next中存放的是cur中的元素逐一遍历到的子结点的字符串,这样的话,我们逐层的遍历,每一次把cur中所有的元素的子结点都遍历结束后,就根据存在next中的子结点的值将字典中对应的值删除,同时利用cur = next来进行下一次遍历,next清空,循环下去直到遍历到的元素中含有endWord或者是字典已经为空时,就可以停止遍历了。

2、如果由路径图回溯得到最短路径

当我们完成上述的遍历过程后,可以得到如下所示的一张反向的邻接表,左列是当前结点而右列是父结点,这张邻接表就可以帮助我们构造从beginWord到endWord的最短路径。
这里写图片描述
利用回溯的想法,我们根据endWord倒序遍历,先取出endWord的父结点,之后根据父结点逐层向上遍历,直到父结点的值与beginWord相同,停止遍历,将之前逐个遍历的值倒序后即为一条从beginWord到endWord的路径了。

代码

class Solution {
public:
    map<string, set<string> >path;
    vector<string> temp;
    vector<vector<string> > result;
    vector<vector<string> > findLadders(string beginWord, string endWord, vector<string>& dict){
        // 这里同样把字典变为set类型,方便删除和查找
        unordered_set<string> wordList;
        for(int i = 0; i < dict.size(); i++) wordList.insert(dict[i]);

        // cur: 之前遍历到的同层的结点
        // next: cur中结点的子结点
        set<string> cur,next;
        cur.insert(beginWord);
        // 先将字典中的beginWord删除,避免出现循环
        if(wordList.find(beginWord) != wordList.end()) wordList.erase(beginWord);
        while(!wordList.empty() && cur.find(endWord) == cur.end()){
            // 逐个遍历当层结点,寻找其子结点
            for(set<string>::iterator it = cur.begin(); it != cur.end(); it++){
                string word = *it;
                for(int i = 0;i < word.length(); i++){
                    for(char j = 'a'; j <= 'z'; j++){
                        string tmp = word;
                        if(tmp[i] == j) continue;
                        tmp[i] = j;
                        // 看与当前字符串差一个字符的tmp是否存在于字典中
                        if(wordList.find(tmp) != wordList.end()){
                            next.insert(tmp);
                            // 记录tmp及其父结点,用于构造反向邻接表
                            path[tmp].insert(word);
                        }
                    }
                }
            }
            if(next.empty()) break; // 说明当前字符串没有子结点,查找结束

            // 将新查找到的子结点从字典中删除
            for(set<string>::iterator it = next.begin(); it != next.end(); it++)
                wordList.erase(*it);

            cur = next;
            next.clear();
        }
        // 从endWord回溯,构造从beginWord到endWord的路径
        if(cur.find(endWord) != cur.end()) 
            pathhelper(endWord,beginWord); 
        return result;

    }
    void pathhelper(string beginWord,string endWord){
        temp.push_back(beginWord);
        if(beginWord == endWord){
            // 新建一个vector变量用于倒序并加入到最终结果中
            vector<string> tt = temp;
            reverse(tt.begin(),tt.end());
            result.push_back(tt);
            return ;

        }
        // 从endWord的父结点开始,逐层向上遍历
        for(set<string>::iterator iter = path[beginWord].begin(); iter != path[beginWord].end(); iter++){
            pathhelper(*iter,endWord);
            temp.pop_back();
        }
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值