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.
进行广度优先遍历,我们可以在发现替换字母后的单词在字典中时,不马上继续处理它,而是将其放入一个队列中。通过队列先进先出的性质,就可以实现广度优先的处理了。由于最后要输出的是整个转换序列,为了简单起见,我们可以将当前已转换出的序列放入队列中,即队列形式为queue<vector<std::string>>,序列的最后一个元素就是下次处理时要继续转换的单词。
使用广度优先遍历后,还有一个特性使得我们可以更方便的处理深度优先中重复单词的问题。当一个单词在某一层(一层即从第一个单词到当前单词的长度一样的序列)出现后,后面再出现的情况肯定不会是最短序列(相当于走了回头路),因此我们可以在处理完一层后直接将已用过的单词从字典中去除。需要注意的是,同一层是不能去除的,如例子中的hot在两个序列的第二层中都出现了。这样我们就需要一个容器把当前层用过的单词保存起来,等处理的层数发生变化时再将它们从字典里移除。
最后要注意的是查找结束的条件。由于找到了一个序列后该序列只是最短的之一,我们还要继续进行处理,直到队列中当前层都处理完毕。所以我们要在找到一个序列后将它的长度记录下来,当要处理的序列长度已经大于该值时,就可以结束查找了(代码中用字符串所在的层数作为长度)。
vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict)
{
queue<vector<string>> candidate;
vector<vector<string>> result;
vector<string> usedWord;
int prePathLen=0;
int shortest = 0;
vector<string> vec;
vec.push_back(start);
candidate.push(vec);
usedWord.push_back(start);
bool foundShortest=false;
while (!candidate.empty())
{
string curStr = *candidate.front().rbegin();
if (candidate.front().size()!=prePathLen)
{
for (int k=0;k<usedWord.size();k++)
{
dict.erase(usedWord[k]);
}
}
if (shortest<prePathLen && foundShortest)
{
break;
}
for (int i=0;i<curStr.size();i++)
{
string tmp = curStr;
for (char ch='a';ch<='z';ch++)
{
tmp[i] = ch;
if (dict.find(tmp)!=dict.end())
{
usedWord.push_back(tmp);
}
if (tmp == end)
{
result.push_back(candidate.front());
result.rbegin()->push_back(end);
foundShortest = true;
shortest = result.rbegin()->size();
}
if (dict.find(tmp)!=dict.end())
{
vector<string> newPath=candidate.front();
newPath.push_back(tmp);
candidate.push(newPath);
}
}
}
prePathLen = candidate.front().size();
candidate.pop();
}
return result;
}
vector<vector<string>> findLadders2(string start, string end, unordered_set<string> &dict)
{
unordered_map<string, vector<string>> prevMap;
for(auto iter = dict.begin(); iter != dict.end(); ++iter)
prevMap[*iter] = vector<string>();
prevMap[start] = vector<string>();
prevMap[end] = vector<string>();
vector<unordered_set<string>> candidates(2);
int current = 0;
int previous = 1;
candidates[current].insert(start);
while(true)
{
current = !current;
previous = !previous;
for (auto iter = candidates[previous].begin(); iter != candidates[previous].end(); ++iter)
dict.erase(*iter);
candidates[current].clear();
for(auto iter = candidates[previous].begin(); iter != candidates[previous].end(); ++iter)
{
for(size_t pos = 0; pos < iter->size(); ++pos)
{
string word = *iter;
for(char ch = 'a'; ch <= 'z'; ++ch)
{
if(word[pos] == ch)
continue;
word[pos] = ch;
if (word == end)
{
prevMap[word].push_back(*iter);
candidates[current].insert(word);
}
if(dict.count(word) > 0)
{
prevMap[word].push_back(*iter);
candidates[current].insert(word);
}
}
}
}
if (candidates[current].count(end))
break;
}
vector<string> path;
vector<vector<string>> result;
GeneratePath(prevMap, path, end,result);
return result;
}
void GeneratePath(unordered_map<string, vector<string>> &prevMap, vector<string> path, string word, vector<vector<string>>& result)
{
if (prevMap[word].size()==0)
{
path.push_back(word);
result.push_back(path);
reverse(path.begin(),path.end());
return;
}
path.push_back(word);
for (int i=0;i<prevMap[word].size();i++)
{
GeneratePath(prevMap, path, prevMap[word][i], result);
}
}