126. Word Ladder II

本文探讨了如何在给定的单词序列中找到从起始单词到结束单词的最短路径,同时确保每一步转换后的单词存在于指定的字典中。通过广度优先搜索(BFS)算法和哈希表的巧妙运用,实现了一种有效的方法来解决这个问题,并提供了完整的代码实现。文章还讨论了如何避免重复计算和构建所有最短路径。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from start to end, such that:

  1. Only one letter can be changed at a time
  2. Each intermediate word must exist in the dictionary

For example,

Given:
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]

Return

  [
    ["hit","hot","dot","dog","cog"],
    ["hit","hot","lot","log","cog"]
  ]

Note:

  • All words have the same length.
  • All words contain only lowercase alphabetic characters.

分析:本题主要的框架和上一题是一样,但是还要解决两个额外的问题:一、 怎样保证求得所有的最短路径;二、 怎样构造这些路径

第一问题:

  • 不能像上一题第二点注意那样,找到一个单词相邻的单词后就立马把它从字典里删除,因为当前层还有其他单词可能和该单词是相邻的,这也是一条最短路径,比如hot->hog->dog->dig和hot->dot->dog->dig,找到hog的相邻dog后不能立马删除,因为和hog同一层的单词dot的相邻也是dog,两者均是一条最短路径。但是为了避免进入死循环,再hog、dot这一层的单词便利完成后dog还是得从字典中删除。即等到当前层所有单词遍历完后,和他们相邻且在字典中的单词要从字典中删除。
  • 如果像上面那样没有立马删除相邻单词,就有可能把同一个单词加入bfs队列中,这样就会有很多的重复计算(比如上面例子提到的dog就会被2次加入队列)。因此我们用一个哈希表来保证加入队列中的单词不会重复,哈希表在每一层遍历完清空(代码中hashtable)
  • 当某一层的某个单词转换可以得到end单词时,表示已经找到一条最短路径,那么该单词的其他转换就可以跳过。并且遍历完这一层以后就可以跳出循环,因为再往下遍历,肯定会超过最短路径长度

第二个问题:

  • 为了输出最短路径,我们就要在比bfs的过程中保存好前驱节点,比如单词hog通过一次变换可以得到hot,那么hot的前驱节点就包含hog,每个单词的前驱节点有可能不止一个,那么每个单词就需要一个数组来保存前驱节点。为了快速查找因此我们使用哈希表来保存所有单词的前驱路径,哈希表的key是单词,value是单词数组。(代码中的unordered_map<string,vector<string> >prePath)                         
  • 有了上面的前驱路径,可以从目标单词开始递归的构造所有最短路径(代码中的函数 ConstructResult
class Solution {
public:
    typedef unordered_set<string>::iterator HashIter;
    vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) {
        // Note: The Solution object is instantiated only once and is reused by each test case.
        queue<string> Q;
        Q.push(start); Q.push("");
        bool hasFound = false;
        unordered_map<string,vector<string> >prePath;//前驱路径
        unordered_set<string> hashtable;//保证bfs时插入队列的元素不存在重复
        while(Q.empty() == false)
        {
            string str = Q.front(), strCopy = str;
            Q.pop();
            if(str != "")
            {
                int strLen = str.length();
                for(int i = 0; i < strLen; i++)
                {
                    char tmp = str[i];
                    for(char c = 'a'; c <= 'z'; c++)
                    {
                        if(c == tmp)continue;
                        str[i] = c;
                        if(str == end)
                        {
                            hasFound = true;
                            prePath[end].push_back(strCopy);
                            //找到了一条最短路径,当前单词的其它转换就没必要
                            goto END;
                        }
                        if(dict.find(str) != dict.end())
                        {
                            prePath[str].push_back(strCopy);
                            //保证bfs时插入队列的元素不存在重复
                            if(hashtable.find(str) == hashtable.end())
                                {Q.push(str); hashtable.insert(str);}
                        }
                    }
                    str[i] = tmp;
                }
            }
            else if(Q.empty() == false)//到当前层的结尾,且不是最后一层的结尾
            {
                if(hasFound)break;
                //避免进入死循环,把bfs上一层插入队列的元素从字典中删除
                for(HashIter ite = hashtable.begin(); ite != hashtable.end(); ite++)
                    dict.erase(*ite);
                hashtable.clear();
                Q.push("");
            }
        END: ;
        }
        vector<vector<string> > res;
        if(prePath.find(end) == prePath.end())return res;
        vector<string> tmpres;
        tmpres.push_back(end);
        ConstructResult(prePath, res, tmpres, start, end);
        return res;
    }
    
private:
    //从前驱路径中回溯构造path
    void ConstructResult(unordered_map<string,vector<string> > &prePath, 
        vector<vector<string> > &res, vector<string> &tmpres, 
        string &start, string &end)
    {
        if(start == end)
        {
            reverse(tmpres.begin(), tmpres.end());
            res.push_back(tmpres);
            reverse(tmpres.begin(), tmpres.end());
            return;
        }
        vector<string> &pre = prePath[end];
        for(int i = 0; i < pre.size(); i++)
        {
            tmpres.push_back(pre[i]);
            ConstructResult(prePath, res, tmpres, start, pre[i]);
            tmpres.pop_back();
        }
        
    }
};

另外这一题如果不用队列来进行bfs,可能会更加方便,使用两个哈希表来模拟队列,这样还可以避免前面提到的同一个元素加入队列多次的问题。

思路:

其实解题思路和Word Ladder完全一样,BFS,但是麻烦的是要返回所有的路径。

所以没办法,只能把每个单词所对应的前驱单词记录下来,当然有可能有多个,那么

就用一个vector<string>存储好,有这些记录就可以重构路径了。原博文见:http://blog.youkuaiyun.com/niaokedaoren/article/details/8884938

class Solution {
public:
    vector<vector<string> > findLadders(string start, string end, unordered_set<string> &dict) {
        // Start typing your C/C++ solution below
        // DO NOT write int main() function
        pathes.clear();
        dict.insert(start);
		dict.insert(end);
		vector<string> prev;
		unordered_map<string, vector<string> > traces;
		for (unordered_set<string>::const_iterator citr = dict.begin(); 
				citr != dict.end(); citr++) {
			traces[*citr] = prev;
		}
		
		vector<unordered_set<string> > layers(2);
		int cur = 0;
		int pre = 1;
		layers[cur].insert(start);
		while (true) {
			cur = !cur;
			pre = !pre;
			for (unordered_set<string>::const_iterator citr = layers[pre].begin();
					citr != layers[pre].end(); citr++)
				dict.erase(*citr);
			layers[cur].clear();
			for (unordered_set<string>::const_iterator citr = layers[pre].begin();
					citr != layers[pre].end(); citr++) {
				for (int n=0; n<(*citr).size(); n++) {  
                    string word = *citr;  
                    int stop = word[n] - 'a';  
                    for (int i=(stop+1)%26; i!=stop; i=(i+1)%26) {  
                        word[n] = 'a' + i;  
                        if (dict.find(word) != dict.end()) {  
                            traces[word].push_back(*citr);
    				        layers[cur].insert(word); 
                        }  
                    }
                }
			}
            if (layers[cur].size() == 0)
                return pathes;
			if (layers[cur].count(end))
				break;
		}
		vector<string> path;
		buildPath(traces, path, end);

		return pathes;
	}

	private:
		void buildPath(unordered_map<string, vector<string> > &traces, 
				vector<string> &path, const string &word) {
			if (traces[word].size() == 0) {
                path.push_back(word);
				vector<string> curPath = path;
				reverse(curPath.begin(), curPath.end());
				pathes.push_back(curPath);
                path.pop_back();
				return;
			}

			const vector<string> &prevs = traces[word];
			path.push_back(word);
			for (vector<string>::const_iterator citr = prevs.begin();
					citr != prevs.end(); citr++) {
				buildPath(traces, path, *citr);
			}
			path.pop_back();
		}

		vector<vector<string> > pathes;
};



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值