Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from start to end, such that:
- Only one letter can be changed at a time
- 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;
};