题目来源
题目描述
题目解析
两个单词只有一个字母之差,它们就是邻接关系,我们可以从wordList中得到一张邻接表
将这张表转换成无向图。邻接节点用一条无向边相连:
题目等价为:在图中找到起点到终点词的最短路径。题目中,起始词没有出现在单词表中,加进入就行。
路径最短意味着什么?hit -> hot -> lot -> log,这是一条转变路径,hit在第0层,hot在第1层……从起始词到终点词经历的层数,是路径的长度。最短路径代表终点词在路径中的层尽量小。
求“最短路径”、“最小深度”——BFS
- 每个单词节点有自己的层,它的“邻居”是它可变成的单词,属于下一层。
- 这很像 BFS:考察当前层出列的节点,带出下一层的节点入列。
怎么找到自己的 “邻居单词”
- 让单词的每个字母逐个改动,生成25个新单词。选出存在于wordList的,就是“邻居单词”。
路径上出现过的单词,就不要让它再出现
- 比如:hit->hot->hit ,又变回来,即重复访问节点,徒增路径长度。用一个 visited 容器,记录遍历过的单词节点。
为什么还需要 DFS?
- 如果是求最短路径的长度,仅 BFS 即可。但要找出最短路径的所有组合,则需要回溯。
- 我们可以从起点词开始DFS,也可以从终点词开始 DFS,我选择后者,遇到起点词,就找到一条满足条件的路径,推入结果数组,然后回溯,直到找出所有最短路径。
不能边计算长度边计算路径吗?不能,原因如下:
class Solution {
public:
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
unordered_set<string> search(wordList.begin(), wordList.end()); // hash 提高转换(查找)效率
vector<vector<string>> res;
deque<vector<string>> worker; // 层节点容器
worker.push_back({beginWord}); // beginWord 作为起始的根节点
while (!worker.empty()) {
unordered_set<string> visited; // 一层内已转化过的 string 容器。set避免重复保存
for (int i = worker.size(); i > 0; --i) { // 层遍历
auto sub = worker.front(); worker.pop_front(); // 获取单个节点
auto tail = sub.back(); // 获取单个节点内的最后一个 string 元素
if (tail == endWord) { // 是在到达是转换的终点
res.push_back(sub);
continue;
}
for (int j = 0; j < tail.size(); ++j) { // 回溯试探 下层 节点的元素的可能性
char temp = tail[j]; // 单个 string 回溯保存状态
for (char c = 'a'; c <= 'z'; ++c) {
if (c == temp) continue; // 忽略原始状态
tail[j] = c;
if (!search.count(tail)) continue; // 未找到转换序
visited.insert(tail); // 找到转换序,保存 已使用 转换
sub.push_back(tail); // 节点 回溯
worker.push_back(sub); // 向容器内保存 子节点
sub.pop_back(); // 回溯恢复
}
tail[j] = temp; // string 回溯恢复状态
}
}
if (res.size()) return res; // 第一次到达 树底,也就是得到最小转换序
for (auto &w : visited) search.erase(w); // 删除已使用的转换序
}
return {};
}
};