126. 单词接龙 II
给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回一个空列表。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出:
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]
示例 2:
输入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
输出: []
解释: endWord "cog" 不在字典中,所以不存在符合要求的转换序列。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/word-ladder-ii
思路
判断两个单词是否能够转换可以抽象成图,能转换即在两个单词之间构建一条无向边。需要一个check()
函数
需要完成两个单词的互相查找,每个单词存在Id
,使用哈希表完成单词到Id
的查找。使用vector
完成Id
到单词的查找。数据结构分别为ordered_map<string,int>,vector<string>
需要edges
来表示单词的连接关系,因为一个单词可与多个单词转换,使用vector<vector<int>>
表示。
因为要求最短路径,使用int cost[]
数组,表示路径长度,如何判断下一个节点是否比当前节点路径短,也就是说下一个节点是否经历过,因为一个节点可以存在多个上节点,也会存在多个下节点。cost[now] + 1<=cost[to]
即表示下一节点还未经历,如果大于,则说明经历过了。
代码
const int INF = 1 << 20;
class Solution {
private:
unordered_map<string,int> wordId;//word到Id的映射
vector<string> idWord;//Id到word的映射
vector<vector<int>> edges;//边
public:
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
int id = 0;
//建立映射
for (const auto& word:wordList){
if (!wordId.count(word)){
wordId[word] = id++;
idWord.push_back(word);
}
}
//如果wordList中不存在endWord,返回{};
if (!wordId.count(endWord))
return {};
//如果wordList不存在beginWord,wordId中需要添加
if (!wordId.count(beginWord)){
wordId[beginWord] = id++;
idWord.push_back(beginWord);
}
//初始化边,edges的类型为vector<vector<int>>,有多少节点就有多少个vector<int>,然后存储与这个节点相接的下一个节点。
edges.resize(idWord.size());
//构建边;双循环,两两检查
for (int i = 0;i < idWord.size();++i){
for (int j = i + 1;j <idWord.size();++j){
//两两检查
if (check(idWord[i],idWord[j])){
//边是双向的,一个节点可以有好几个下节点
edges[i].push_back(j);
edges[j].push_back(i);
}
}
}
const int dest = wordId[endWord];//终点的标志数值
vector<vector<string>> res;//结果
queue<vector<int>> q;//深度优先搜索,队列,存储路径数组
vector<int> cost(id,INF);//每条路径的长度,初始化为无限长
q.push(vector<int>{wordId[beginWord]});//初始化队列,从beginWord开始
cost[wordId[beginWord]] = 0;//长度从0开始
//队列空,表示遍历了所有节点
//如何判断是否是最小路径,如果有一条路径到达终点,和此条路径同时放入队列的必然是同一长度,且不会有新的路径放入队列,这就实现了最小路径。
while (!q.empty()){
//当前路径数据
vector<int> now = q.front();
q.pop();
//当前路径最后一个节点
int last = now.back();
//若等于endWord的Id,输出当前路径
if (last == dest) {
vector<string> tmp;
for (int index : now) {
tmp.push_back(idWord[index]);
}
res.push_back(tmp);
}else {//若不等于,往下遍历节点,根据存储在edges中的顺序
for (int i = 0; i < edges[last].size(); i++){
int to = edges[last][i];
//如果下一个节点经历过,长度必不可能大于当前节点长度+1,没有经历过,长度加1。将下一节点放入当前路径数组,将当前路径数组放入队列。
if (cost[last] + 1 <= cost[to]) {
cost[to] = cost[last] + 1;
vector<int> tmp(now);
tmp.push_back(to);
q.push(tmp);
}
}
}
}
return res;
}
//检查函数
bool check(string& a,string& b){
int diff = 0;
for (int i = 0;i < a.size()&&diff < 2;++i){
if (a[i]!=b[i])
diff++;
}
return diff == 1;
}
};