1. 题目来源
链接:126. 单词接龙 II
2. 题目说明
3. 题目解析
本题很难。考查图论建图、单源最短路、bfs
求解边权为 1 的最短路。
思路:
- 将每个单词看成图论中的一个点,如果这个单词能在一步之内变成某个单词。那么就在这两个单词中连一条边。这样初步就能建出一个无向图,将其转化为单源最短路问题,且由于边权为 1,那么
bfs
求解最短路就可以使用了。 - 最小转化步数即为最短路,就可以求解得到了。但是本题是需要输出所有方案…
类似于上图,那么这个输出的方案数就是指数级别的。所以使用dfs
暴力搜索的话就是指数级别的。所以不管怎么优化,答案数量都是指数级别的,所以必须考虑剪枝。 - 建立
dist
数组,dist[i]
表示从起点到i
的最短路,可以通过bfs
搜索得到所有点到起点的最短路 - 利用
dist
数组暴搜出所有最短路径。采用反向搜索, 从终点T
开始搜索,枚举所有邻点v
,若dist[v] + 1 == dist[T]
,说明存在一条可能的最短路径,那么我们就走到v
去,即递归dfs(v)
。这样就不会去搜索不合法方案,是一步很厉害的剪枝 - 最后一步是从
v
走到T
,当dfs
参数与起点相同就说明我们成功的从终点沿着最短路找到了起点,找到了这条答案路径。返回路径数组即可
两种建图方式:
- 可以两两枚举两个单词,查看是否仅有一个单词不同,这样的建图方式就是 O ( n 2 L ) O(n^2L) O(n2L)
- 也可以每个单词的每一个字母总共 26 种情况变换,再将变换单词插入到哈希表中,需要进行查询其是否已经存在。总共的时间复杂度为 O ( 26 n L 2 ) O(26nL^2) O(26nL2)
- 所以,对比两种建图方式,当
n>= 26L
时,采用第二种建图方式,否则采用第一种方式比较好
这题是很棒的一道图论问题,考点还是很全面的。从问题抽象,建图,最短路,确实很有价值!
一道很是类似的题目,推荐:[bfs+图论] 八数码(建图+bfs最短路+思维)
代码:
class Solution {
public:
unordered_set<string> S;
unordered_map<string, int> dist;
queue<string> q;
vector<vector<string>> ans;
vector<string> path;
string start;
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
start = beginWord;
for (auto word:wordList) S.insert(word);
dist[beginWord] = 0;
q.push(beginWord);
while (q.size()) {
auto t = q.front();
q.pop();
string r = t; // 缓存t字符串,防止改变影响后序
for (int i = 0; i < t.size(); ++i) { // 枚举当前单词每一位
t = r; // 每次拿到初始的t
for (char j = 'a'; j <= 'z'; ++j) { // 枚举当前单词改变的26种情况
t[i] = j; // 改变当前位,t字符串更新
if (S.count(t) && dist.count(t) == 0) { // t是一次成功更新,且dist没有t,说明r之前没有边到t
dist[t] = dist[r] + 1; // 更新最短路距离+1
if (t == endWord) break; // 如果t是终点则不必要找终点的转化,直接break
q.push(t); // t更新后入队
}
}
}
}
if (dist.count(endWord) == 0) return ans; // 如果终点不在图中,直接返回
path.push_back(endWord); // 将终点加入答案中
dfs(endWord); // dfs从终点开始搜
return ans;
}
// 这里是从终点往起点搜,故ans存的是逆序
void dfs(string t) {
if (t == start) { // 如果当前点是起始点
reverse(path.begin(), path.end());
ans.push_back(path);
reverse(path.begin(), path.end());
} else {
string r = t; // 当前单词
for (int i = 0; i < t.size(); ++i) { // 枚举当前单词所转化的所有单词
t = r;
for (int j = 'a'; j <= 'z'; ++j) {
t[i] = j;
if (dist.count(t) && dist[t] + 1 == dist[r]) { // 新t存在且在可能的最短路径中
path.push_back(t); // 将t加入答案
dfs(t); // 以t作为终点,dfs处理
path.pop_back(); // 回溯
}
}
}
}
}
};