2020年9月26日 周六 天气晴 【不悲叹过去,不荒废现在,不惧怕未来】
解题要点
- 无向图中两个顶点之间的最短路径的长度,可以通过广度优先遍历得到;
- 借助哈希表判断是否单词之间是否存在路径(只有一个字母不同);
- 已知目标顶点的情况下,可以分别从起点和目标顶点(终点)执行广度优先遍历(每次都从节点数比较小的那一方开始遍历),直到遍历的部分有交集,这是双向广度优先遍历的思想。
1. 图的BFS(单向)
class Solution{
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList){
// 用原始数组初始化dict,访问过之后要删除,避免重复访问
unordered_set<string> dict(wordList.begin(),wordList.end());
if (dict.find(endWord) == dict.end()) return 0;
queue<pair<string,int>> q; // BFS需要的队列
q.push(make_pair(beginWord,1)); // 加入beginWord
string tmp; // 当前访问的节点
int step; // 抵达该节点时的step
while(!q.empty()){
auto p = q.front();
// 寻找到endWord了
if(p.first==endWord) return p.second;
tmp = p.first;
step = p.second;
q.pop();
// 开始寻找下一个节点
char old_ch;
for(int i=0;i<tmp.size();++i){
old_ch= tmp[i];
// 从'a'-'z'都尝试一次
for(char c ='a';c<='z';++c){
// 字符相同,提前结束
if(c==old_ch) continue;
tmp[i] = c;
// 如果能找到,说明存在路径(转换)
if(dict.find(tmp)!=dict.end()){
q.push(make_pair(tmp,step+1)); // 加入到下一层中
dict.erase(tmp); // 删除该节点
}
}
tmp[i] = old_ch; // 复原
}
}
return 0;
}
};
- 时间复杂度: O ( m n ) O\left( {mn} \right) O(mn),其中 m m m是单词的长度, n n n是单词表中单词的总数。
- 空间复杂度: O ( m n ) O\left( {mn} \right) O(mn)
2. 图的BFS(双向)
具体做法:
- 使用两个set,分别从start和end两头开始BFS;
- 每次选择较小的set开始BFS,也就是将小的作为start,大的作为end;
- 如果end中能找到start,说明出现了交集,遍历结束;否则,在访问set中加入访问记录,并加入到tmp中,作为子节点。
class Solution{
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList){
// 用原始数组初始化dict
unordered_set<string> dict(wordList.begin(),wordList.end());
if (dict.find(endWord) == dict.end()) return 0;
// 从起点和终点同时进行遍历
unordered_set<string> begSet, endSet, tmp, visited;
begSet.insert(beginWord);
endSet.insert(endWord);
int len = 1;
// 两头同时进行遍历,直到有一个set为空,说明不存在beginWord到endWord的路径(转换),返回0
while(!begSet.empty() && !endSet.empty()){
// 始终让begSet里的单词个数最小
if(begSet.size() > endSet.size()){
tmp = begSet;
begSet = endSet;
endSet = tmp;
}
tmp.clear();
// 遍历单词个数较小的begSet
for(string word:begSet){
// 更改每个位置的字母,看dict中是否有匹配的word,有的话说明能建立路径
for(int i=0;i<word.size();++i){
char old_ch = word[i];
for(char c='a';c<='z';++c){
if(c==old_ch) continue;
word[i] = c;
// 如果当前这个word出现在endSet,说明出现了交集,遍历结束,返回结果:len+1
if(endSet.find(word)!=endSet.end())
return len+1;
// 没有出现的话,根据是否遍历过、是否存在于dict进行处理
if(dict.find(word)!=dict.end() && visited.find(word)==visited.end()){
visited.insert(word);
tmp.insert(word);
}
}
word[i] = old_ch; // 还原
}
}
++len; // 又进行了一层遍历,len自加1
begSet = tmp; // 将新的一层节点赋值给begSet
}
return 0;
}
};
- 时间复杂度: O ( m n ) O\left( {mn} \right) O(mn),其中 m m m是单词的长度, n n n是单词表中单词的总数。虽然渐进时间复杂度和前一个解法相同,但是因为是双向遍历,实际运行时间更短,空间复杂度分析类似。
- 空间复杂度: O ( m n ) O\left( {mn} \right) O(mn)
参考文献
https://leetcode-cn.com/problems/word-ladder/solution/cpp-yi-ge-si-lu-de-zhuan-bian-cong-1156msjia-su-da/
https://leetcode-cn.com/problems/word-ladder/solution/yan-du-you-xian-bian-li-shuang-xiang-yan-du-you-2/