给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入:
beginWord = “hit”,
endWord = “cog”,
wordList = [“hot”,“dot”,“dog”,“lot”,“log”,“cog”]
输出: 5
解释: 一个最短转换序列是 “hit” -> “hot” -> “dot” -> “dog” -> “cog”,
返回它的长度 5。
示例 2:
输入:
beginWord = “hit”
endWord = “cog”
wordList = [“hot”,“dot”,“dog”,“lot”,“log”]
输出: 0
解释: endWord “cog” 不在字典中,所以无法进行转换。
通过次数46,730提交次数109,825
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/word-ladder
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路:
这是一道图的广度优先搜索,将字符能够相互转化视为图中两个节点所相连的边,此时通过以beginWord出发进行广度优先搜索遍历,期间记录遍历到每个位置的步数,可以获得最短的长度。难点有三个,一是将字符集抽象为图并选择合适的数据结构进行图的构建;二是此题目可以通过双向bfs来优化宽度优先搜索的时间复杂度;三是广度优先遍历过程中的已访问的设置,和搜索结束的条件设置。下面分别说明三个难点的解决方式:
1. 对于图的构建,可以使用map,key值中存储字符串,而value中存储为与该字符串相连即能够以题目条件相互转化的字符串的vector,亦即是以邻接表的方式构建图。因此需要实现一个判断是否相连的函数,之后双层遍历wordlist中的所有字符串,构建所有字符串的邻接表即可。需要注意的是,需要将beginword加入wordlist。
2. 双向bfs应用的场景是知道遍历要找的路径的起点和终点,分别从两边向中间进行遍历,当两边访问到相同的节点的时候,即说明找到了一条最短的路径,记录此时的长度即可。相对于单向的bfs,双向bfs优化了搜索空间,降低了时间复杂度。在实现的时候,需要设置两个遍历所需的队列,同时,为了减少遍历结束的条件判断的复杂度,需要一定的规则。本题中,默认先对一个队列进行中的队头元素进行bfs,将它的所有相邻接点入队并修改访问记录数组,同时,在每个元素入队的时候在另一个访问数组中查找这个元素,如果找到则说明满足退出条件,记录此时的结果,没有找到的话修改访问记录数组,注意:访问记录数组中需要同时记录到这个节点的步数。之后对另一个队头元素做相同的操作直至遍历结束或者找到结果。
3. 在本题中,无论是双向bfs和单向bfs都需要特别的设置访问数组,因为需要记录遍历到当前节点所走的步数,以便返回最后的结果。而对于本题的双向bfs,需要频繁的进行元素的查找。因此使用
stl::set(pair<string,int>)作为访问数组,而搜索结束的条件在2中已经详细说明。
ps:我的解法里,双向bfs仅仅快了400ms,大概20%,大概是太垃圾了吧,同时,写代码的时候要注意细节,在判断队列是否为空的时候切记要检查叹号是不是该用,特别是要同时判断两个队列的时候,少打一个叹号找了半小时。淦!
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
queue<pair<string,int>> q1;
queue<pair<string,int>> q2;
unordered_map<string,int> fv;
unordered_map<string,int> bv;
unordered_map<string,vector<string>> graph;
constructGraph(wordList,beginWord,graph);
q1.push(pair<string,int>(beginWord,1));
q2.push(pair<string,int>(endWord,1));
fv[beginWord] = 1;
bv[endWord] = 1;
while(!q1.empty()&&!q2.empty())
{
int ret = visitWordNode(q1,fv,bv,graph);
if(ret>-1)return ret;
ret = visitWordNode(q2,bv,fv,graph);
if(ret>-1)return ret;
//cout<<ret<<endl;
}
return 0;
}
int visitWordNode(queue<pair<string,int>>& Q,unordered_map<string,int>& visit,unordered_map<string,int>& anovisit,
unordered_map<string,vector<string>>& graph)
{
string tempStr = Q.front().first;
int step = Q.front().second;
Q.pop();
for(auto str : graph[tempStr])
{
if(anovisit.find(str)!=anovisit.end())
{
return step+anovisit[str];
}
if(visit.find(str)==visit.end())
{
Q.push(pair<string,int>(str,step+1));
visit[str] = step+1;
}
}
return -1;
}
bool isConnect(string &a,string &b)
{
int len = a.size();
int count = 0;
for(int i = 0;i<len;i++)
{
if(a[i]!=b[i])
{
count++;
}
}
if(count==1)return true;
return false;
}
void constructGraph(vector<string>& wordstring,string &beginWord,
unordered_map<string,vector<string>> &graph)
{
wordstring.push_back(beginWord);
for(int i = 0;i<wordstring.size();i++)
{
graph[wordstring[i]] = vector<string>();
}
for(int i = 0;i<wordstring.size();i++)
{
for(int j = i+1;j<wordstring.size();j++)
{
if(isConnect(wordstring[i],wordstring[j]))
{
graph[wordstring[i]].push_back(wordstring[j]);
graph[wordstring[j]].push_back(wordstring[i]);
}
}
}
}
};