给定两个单词(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
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
不同于【LeetCode:127 单词接龙】,这题不是求最短长度,而是求所有的最短序列,这就比较蛋疼棘手 了
因为要求所有的最短的序列,那么肯定要bfs,而且bfs的上界就是当前长度 = 最短序列的长度(上面那个题求的,已知)
那么直接dfs呢?会超时,这里有一个非常巧妙的剪枝:
在bfs的时候,我们 不再使用双端bfs ,而是用从起点出发的bfs,对于每次bfs到的节点,我们都记录它第一次出现的深度,方便我们等下dfs的时候用
如果dfs的时候,遇到一个点,他的深度不是我们bfs记录过的深度,说明这条路没有按照我们bfs时的策略来走,肯定不是最短,这个时候直接放弃搜索
如图:2节点在bfs时第一次出现是在第二层,如果我们在第二层以外的层碰到2节点,说明走的不是最短路,直接放弃这个分支
这个优化使得代码直接可以AC,非常重要的剪枝
代码
class Solution {
public:
int len;
vector<vector<string>> ans;
unordered_map<string, int> map; // 哈希map存层数
// bfs求最短距离顺便标记节点第一次出现的层数
int ladderLength(string beginWord, string endWord, vector<string>& wordList)
{
unordered_set<string> reachable(wordList.begin(), wordList.end());
if(reachable.find(endWord) == reachable.end()) return 0;
unordered_set beginSet {beginWord};
int ans = 1;
while(!beginSet.empty())
{
unordered_set<string>::iterator i;
for(i=beginSet.begin(); i!=beginSet.end(); i++) reachable.erase(*i);
ans += 1;
unordered_set<string> this_step;
for(i=beginSet.begin(); i!=beginSet.end(); i++)
{
for(int j=0; j<(*i).length(); j++)
{
string s = *i;
for(char c='a'; c<='z'; c++)
{
s[j] = c;
if(reachable.find(s) != reachable.end())
{
this_step.insert(s);
map[s] = ans;
if(s==endWord) return ans;
}
}
}
}
beginSet = this_step;
}
return 0;
}
// dfs+剪枝 unv = un visited = 没有被访问过的点的集合
void dfs(int l, string& endWord, vector<string> &Ans, unordered_set<string>& unv)
{
if(l>len) return;
if(l==len && Ans.back()==endWord) {ans.push_back(Ans); return;}
string bk = Ans.back();
for(int pos=0; pos<bk.length(); pos++)
{
for(char c='a'; c<='z'; c++)
{
string s=bk; s[pos]=c;
if(unv.find(s)!=unv.end())
{
// 重要剪枝:如果不是在已知的层出现,说明不是最短,跳过
if(map[s]!=l+1) continue;
unv.erase(s);
Ans.push_back(s);
dfs(l+1, endWord, Ans, unv);
Ans.pop_back();
unv.insert(s);
}
}
}
}
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList)
{
len = ladderLength(beginWord, endWord, wordList);
if(len==0) return ans;
vector<string> Ans{beginWord};
unordered_set<string> unv(wordList.begin(), wordList.end());
dfs(1, endWord, Ans, unv);
return ans;
}
};