leetcode#127 单词接龙
题目:
字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列:
- 序列中第一个单词是 beginWord 。
- 序列中最后一个单词是 endWord 。
- 每次转换只能改变一个字母。
- 转换过程中的中间单词必须是字典 wordList 中的单词。
给你两个单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。
示例:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5
解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
思路:
和上一个题一样,bfs。
上一个题目中我们对当前层的一个节点,判断哪些是他的子节点的时候,我们要遍历一遍整个wordList,看看哪个单词没有被加入队列过并且这个单词和当前单词只差一个字母,效率十分的低。
在这道题中,我们可以进行建图优化,直接建图,只有相差一个字母的才能连接到一起,用哈希表存储他们的所有状态,有相同状态的会连接到一起,省去比较的过程。
具体地,我们可以创建虚拟节点。对于单词 hit,我们创建三个虚拟节点 * it、h * t、hi *,并让 hit 向这三个虚拟节点分别连一条边即可。如果一个单词能够转化为 hit,那么该单词必然会连接到这三个虚拟节点之一。对于每一个单词,我们枚举它连接到的虚拟节点,把该单词对应的 id 与这些虚拟节点对应的 id 相连即可。
最后我们将起点加入队列开始广度优先搜索,当搜索到终点时,我们就找到了最短路径的长度。注意因为添加了虚拟节点,所以我们得到的距离为实际最短路径长度的两倍。同时我们并未计算起点对答案的贡献,所以我们应当返回距离的一半再加一的结果。
代码:
class Solution
{
public:
int cnt = 0;
unordered_map<string, int> mp;
vector<vector<int>> edge;
void addword(string s)
{
if (!mp.count(s))
mp[s] = cnt++;
edge.emplace_back();
}
void addedge(string s)
{
int len = s.length();
addword(s);
int id_s = mp[s];
for (int i = 0; i < len; ++i)
{
char tmp = s[i];
s[i] = '*';
addword(s);
int id_tmp = mp[s];
edge[id_s].push_back(id_tmp);
edge[id_tmp].push_back(id_s);
s[i] = tmp;
}
}
int ladderLength(string beginWord, string endWord, vector<string> &wordList)
{
int len = wordList.size(), step = 0;
addedge(beginWord);
for (int i = 0; i < len; ++i)
addedge(wordList[i]);
if (!mp.count(endWord))
return 0;
bool vis[cnt];
memset(vis,false,sizeof(vis));
vis[mp[beginWord]] = true;
queue<int> qu;
qu.push(mp[beginWord]);
while (!qu.empty())
{
++step;
int size = qu.size();
for (int i = 0; i < size; ++i)
{
int now = qu.front();
qu.pop();
if (now == mp[endWord])
return step / 2 + 1;
for (int i = 0; i < edge[now].size(); ++i)
{
if (!vis[edge[now][i]])
qu.push(edge[now][i]);
vis[edge[now][i]] = true;
}
}
}
return 0;
}
};