题目
按字典 wordList
完成从单词 beginWord
到单词 endWord
转化,一个表示此过程的 转换序列 是形式上像 beginWord -> s1 -> s2 -> ... -> sk
这样的单词序列,并满足:
- 每对相邻的单词之间仅有单个字母不同。
- 转换过程中的每个单词
si
(1 <= i <= k
)必须是字典wordList
中的单词。注意,beginWord
不必是字典wordList
中的单词。 sk == endWord
给你两个单词 beginWord
和 endWord
,以及一个字典 wordList
。请你找出并返回所有从 beginWord
到 endWord
的 最短转换序列 ,如果不存在这样的转换序列,返回一个空列表。每个序列都应该以单词列表[beginWord, s1, s2, ..., sk]
的形式返回。
示例 1:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:[["hit","hot","dot","dog","cog"],["hit","hot","lot","log","cog"]]
解释:存在 2 种最短的转换序列:
"hit" -> "hot" -> "dot" -> "dog" -> "cog"
"hit" -> "hot" -> "lot" -> "log" -> "cog"
示例 2:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
输出:[]
解释:endWord "cog" 不在字典 wordList 中,所以不存在符合要求的转换序列。
提示:
1 <= beginWord.length <= 5
endWord.length == beginWord.length
1 <= wordList.length <= 500
wordList[i].length == beginWord.length
beginWord
、endWord
和wordList[i]
由小写英文字母组成beginWord != endWord
wordList
中的所有单词 互不相同
思路
首先把字典存起来方便查找,如果目标单词不在字典里,就直接结束,然后先用广度优先搜索,从起始单词开始,逐层尝试改变每个位置的字母,看新单词是否在字典里。在这个过程中,把已用过的单词从字典去掉,避免重复,同时,构建一个反向图,记录每个符合条件的新单词是由哪个旧单词变来的。一旦找到目标单词,就不再继续扩展。 当bfs找到了目标单词,就说明存在最短序列,然后利用深搜和回溯,依据反向图从目标单词反向查找,把经过的单词依次添加到路径中,当回到起始单词时,就找到了一条完整的转换序列。
代码
cpp
class Solution {
public:
unordered_set<string> dict, curLevel, nextLevel;
unordered_map<string, vector<string>> graph;//反向图,存储每个单词的前一个可能的单词
deque<string> path;//用于记录当前的转换路径
vector<vector<string>> ans;//存储所有从beginWord到endWord的最短转换序列。
//初始化函数,将wordList存储到dict中
void build(vector<string>& wordList)
{
dict = unordered_set<string>(wordList.begin(), wordList.end());
graph.clear();
ans.clear();
curLevel.clear();
nextLevel.clear();
}
//找到所有从beginWord到endWord的最短转换序列
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
build(wordList);//初始化
if (dict.find(endWord) == dict.end())
{
return ans;
}
if (bfs(beginWord, endWord))//先调用bfs广搜,找到endword就深搜,找生成路径
{
dfs(endWord, beginWord);
}
return ans;
}
bool bfs(string begin, string end)//广搜从beginWord开始逐层扩展,构建反向图
{
bool find = false;
curLevel.insert(begin);
while (!curLevel.empty())
{
//移除当前层的单词
for (const string& word : curLevel)
{
dict.erase(word);//避免重复使用
}
for (const string& word : curLevel)
{
string w = word;
for (int i = 0; i < w.size(); i++)
{
char old = w[i];
//每个位置的字符依次替换成a到z,检查替换后的单词是否在dict且不等于原单词
for (char ch = 'a'; ch <= 'z'; ch++)
{
w[i] = ch;
if (dict.find(w) != dict.end() && w != word)
{
if (w == end) {
find = true;
}
graph[w].push_back(word);
nextLevel.insert(w);
}
}
w[i] = old;
}
}
if(find)
{
return true;
}
else//没找到endword
{
swap(curLevel, nextLevel);//交换curLevel和nextLevel,继续下一层的扩展
nextLevel.clear();
}
}
return false;
}
//回溯生成路径
void dfs(string word, string aim) {
path.push_front(word);
if(word==aim)
{
ans.push_back(vector<string>(path.begin(), path.end()));
}
else if (graph.find(word)!=graph.end())
{
for(const string& next:graph[word])
{
dfs(next,aim);
}
}
path.pop_front();
}
};