1、题目描述
给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:
- 每次转换只能改变一个字母。
- 转换过程中的中间单词必须是字典中的单词。
说明:
- 如果不存在这样的转换序列,返回一个空列表。
- 所有单词具有相同的长度。
- 所有单词只由小写字母组成。
- 字典中不存在重复的单词。
- 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
2、示例
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出:
[ ["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"] ]
3、题解
问题抽象成把所有单词看成结点,相差一个字符的两个单词就是相邻的节点,给定起始节点和终止节点,找最短路径。本质上就是在正常的遍历的基础上,去剪枝,从而提高速度,如果使用简单的递归DFS,当数据达到一定程度超时。
- 剪枝思想(核心思想):在考虑第
k
层的某一个单词,如果这个单词在第1
到k-1
层已经出现过,我们其实就不用继续向下探索了,该序列路径一定不是最短转换序列。
解法一:DFS+剪枝(剪枝方法:BFS、map、set)
利用BFS层次遍历建立起当前层每个单词与相邻的下一层单词的映射表HashMap,且这些相邻的单词在之前层没有出现过(这也是剪枝的思想,为了防止在DFS的时候搜索多余无用的结果),为了记录这些相邻的单词在之前层没有出现过,使用mindepth建立单词与最先出现的层数之间的映射,为了查找当前单词cur相邻的下一层单词,建立wordList的set表dict,改变当前单词cur的每一位字符从a到z,改变之后在dict里面查找是否存在,这种方法查找当前单词cur相邻的下一层单词相比于遍历wordList每个单词时间复杂度小很多,尤其是在wordList很大的时候。另外使用unordered_map和unordered_set而不是map和set,否则超时。
- bfs层次遍历,构建HashMap用于保存与当前单词cur相邻的所有单词的映射表
- 先把该层的所有单词记录深度,这时mindepth记录的才是单词最先出现的层数
- 遍历当前层所有单词,记录每个单词相邻的在之前层没有出现过所有单词
- 查找当前单词cur的下一个转换的单词:和cur只相差一个字母
- 考虑第k层的某一个单词,如果这个单词在第1到k-1层已经出现过,就不用继续向下探索
- dfs查找所有转换单词序列情况
- 递归终止条件:当前单词cur就是目标单词
- 当前单词为cur,利用HashMap映射表查找下一个转换的单词
解法二:BFS
解法一中 DFS 借助了 BFS 把所有的邻接关系保存了起来,再用 DFS 进行深度搜索。我们也可以只用 BFS,一边进行层次遍历,一边就保存转换单词序列路径。当到达结束单词的时候,就把当前转换单词序列路径存储到最后的返回结果res,省去再进行 DFS 的过程。BFS 的队列就不去存储 string 了,直接去存到目前为止的路径,也就是一个vector<string>。和解法一还有一点不同的是在HashMap里面出现过的单词cur还是要查找cur的下一个转换的单词,因为这是查找所有可能的转换序列,不同于解法一只是为了得到HashMap。
解法三:双向BFS搜索
#include<iostream>
#include<vector>
#include<algorithm>
#include<unordered_map>
#include<list>
#include<unordered_set>
using namespace std;
class Solution {
public:
unordered_map<string, vector<string>> HashMap; //HashMap用于保存与当前单词cur相邻的所有单词的映射表
vector<vector<string>> res; //res保存所有转换单词序列情况
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
//基本思想:DFS+剪枝(剪枝方法:BFS、map、set)
vector<string> ans; //ans保存某一种转换单词序列情况
ans.push_back(beginWord);
bfs(beginWord, endWord, wordList); //bfs构建HashMap
dfs(endWord, ans, wordList); //dfs查找所有转换单词序列情况
return res;
}
void bfs(string beginWord, string endWord, vector<string>& wordList)
{
int depth = 0, flag = 0;
list<string> queue; //queue用于层次遍历
unordered_map<string, int> mindepth; //mindepth用于记录当前单词cur最先出现的层数
unordered_set<string> dict(wordList.begin(), wordList.end()); //空间换时间,用于查找cur相邻的单词
queue.push_front(beginWord);
//层次遍历,构建HashMap用于保存与当前单词cur相邻的所有单词的映射表
while (!queue.empty())
{
int len = queue.size();
depth++;
//先把该层的所有单词记录深度,这时mindepth记录的才是单词最先出现的层数
for (auto i=queue.begin();i!=queue.end();i++)
{
if (mindepth.find(*i) == mindepth.end())
mindepth[*i] = depth;
}
//遍历当前层所有单词,记录每个单词相邻的在之前层没有出现过所有单词
for (int i = 0; i < len; i++)
{
string cur = queue.back();
queue.pop_back();
if (HashMap.find(cur) == HashMap.end())
{
//查找当前单词cur的下一个转换的单词:和cur只相差一个字母
for (int j = 0; j < cur.size(); j++)
{
for (char c = 'a'; c <= 'z'; c++)
{
string temp = cur;
if(cur[j]==c)
continue;
char oldc = cur[j];
cur[j] = c;
if (dict.find(cur) != dict.end()&&mindepth.find(cur) == mindepth.end())
{
//考虑第k层的某一个单词,如果这个单词在第1到k-1层已经出现过,就不用继续向下探索
HashMap[temp].push_back(cur);
queue.push_front(cur);
}
cur[j] = oldc;
}
}
}
}
}
}
void dfs(string endWord, vector<string> ans, vector<string>& wordList)
{
string cur = ans[ans.size() - 1];
//递归终止条件:当前单词cur就是目标单词
if (cur == endWord)
{
res.push_back(ans);
return;
}
//当前单词为cur,利用HashMap映射表查找下一个转换的单词
for (auto v : HashMap[cur])
{
ans.push_back(v);
dfs(endWord, ans, wordList);
ans.pop_back();
}
return;
}
};
class Solution1 {
public:
unordered_map<string, vector<string>> HashMap; //HashMap用于保存与当前单词cur相邻的所有单词的映射表
vector<vector<string>> res; //res保存所有转换单词序列情况
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
//基本思想:BFS
bfs(beginWord, endWord, wordList);
return res;
}
void bfs(string beginWord, string endWord, vector<string>& wordList)
{
int depth = 0, flag = 0;
list<vector<string>> queue; //queue用于层次遍历
vector<string> ans; //ans保存某一种转换单词序列情况
ans.push_back(beginWord);
unordered_map<string, int> mindepth; //mindepth用于记录当前单词cur最先出现的层数
unordered_set<string> dict(wordList.begin(), wordList.end()); //空间换时间,用于查找cur相邻的单词
queue.push_front(ans);
//层次遍历,构建HashMap用于保存与当前单词cur相邻的所有单词的映射表
while (!queue.empty())
{
int len = queue.size();
depth++;
//先把该层的所有单词记录深度,这时mindepth记录的才是单词最先出现的层数
for (auto i = queue.begin(); i != queue.end(); i++)
{
if (mindepth.find((*i).back()) == mindepth.end())
mindepth[(*i).back()] = depth;
}
//遍历当前层所有单词,记录每个单词相邻的在之前层没有出现过所有单词
for (int i = 0; i < len; i++)
{
vector<string> ans = queue.back();
string cur = ans.back();
queue.pop_back();
if (cur == endWord)
{
res.push_back(ans);
flag = 1;
}
//查找当前单词cur的下一个转换的单词:和cur只相差一个字母
for (int j = 0; j < cur.size(); j++)
{
for (char c = 'a'; c <= 'z'; c++)
{
string temp = cur;
if (cur[j] == c)
continue;
char oldc = cur[j];
cur[j] = c;
if (dict.find(cur) != dict.end())
{
//考虑第k层的某一个单词,如果这个单词在第1到k-1层已经出现过,就不用继续向下探索
if (mindepth.find(cur) == mindepth.end())
{
HashMap[temp].push_back(cur);
ans.push_back(cur);
queue.push_front(ans);
ans.pop_back();
}
}
cur[j] = oldc;
}
}
}
if (flag == 1)
break;
}
}
};
class Solution2 {
public:
//保存所有转换单词序列情况
vector<vector<string>> res;
//最短的转换单词序列的长度
int minlength=INT_MAX;
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
//基本思想:单纯递归DFS,超时
vector<string> ans; //ans保存某一种转换单词序列情况
ans.push_back(beginWord);
Recursion(endWord, ans, wordList);
return res;
}
void Recursion(string endWord, vector<string> ans, vector<string>& wordList)
{
//剪枝效果不佳,因为如果终点单词在比较深的层,那么dfs的规模仍然很庞大
if (ans.size() > minlength)
return;
string cur = ans[ans.size() - 1];
//递归终止条件:当前单词cur就是目标单词
if (cur == endWord)
{
//只保留最短的转换单词序列
if (res.empty())
{
res.push_back(ans);
minlength = ans.size();
}
else
{
if (ans.size() < minlength)
{
res.clear();
res.push_back(ans);
minlength = ans.size();
}
else if (ans.size() == minlength)
{
res.push_back(ans);
}
}
return;
}
//当前单词为cur,查找下一个转换的单词
for (int i = 0; i < wordList.size(); i++)
{
int cnt = 0, flag = 0;
//下一个转换的单词的要求:1、和当前单词cur只相差一个字母 2、还没在转换单词序列保存器ans中出现过
//判断下一个转换的单词是否与当前单词cur只相差一个字母
for (int j = 0; j < cur.size(); j++)
{
if (cur[j] == wordList[i][j])
cnt++;
}
if (cnt == cur.size() - 1)
{
//判断下一个转换的单词是否已经在ans中出现
for (auto& s : ans)
{
if (s == wordList[i])
{
flag = 1;
break;
}
}
if(flag==1)
continue;
else
{
ans.push_back(wordList[i]);
Recursion(endWord, ans, wordList);
ans.pop_back();
}
}
}
return;
}
};
int main()
{
Solution1 solute;
string beginWord = "red";
string endWord = "tax";
vector<string> wordList = { "ted","tex","red","tax","tad","den","rex","pee" };
vector<vector<string>> res = solute.findLadders(beginWord, endWord, wordList);
cout << endl;
cout << endl;
cout << endl;
for (int i = 0; i < res.size(); i++)
{
for_each(res[i].begin(), res[i].end(), [](const string& s) {cout << s << " "; });
cout << endl;
}
return 0;
}