前言:这道题相比上一道要更加困难,因此也要想办法改进算法。
1. Word Ladder II
介绍:
Given two words (beginWord and endWord), and a dictionary’s word list, find all shortest transformation sequence(s) from beginWord to endWord, such that:
Only one letter can be changed at a time
Each transformed word must exist in the word list. Note that beginWord is not a transformed word.word.
题意:
与上一道题目一样,只不过上一道题目是求出最短路径长度就够了,这道题则是要把所有的最短路径罗列出来。
举例:
beginWord = “hit”
endWord = “cog”
wordList = {“hot”,”dot”,”dog”,”lot”,”log”,”cog”}
返回:
[
[“hit”,”hot”,”dot”,”dog”,”cog”],
[“hit”,”hot”,”lot”,”log”,”cog”]
]
思路:
仍然是用BFS,注意这次返回的是二重vector迭代器,第二层迭代里面是单纯的字符串,所以不能再使用pair了,并且pair来记录步数也十分的麻烦。
仍然按照上一道题目的思路,先把起点beginWord装进vector中,再把这个vector装进队列paths中,队列中每一个vector都是一条路径,我们要做的就是每次都从paths里面取出一个vector,然后根据这个vector最后一个节点来检索下一步的走法(仍然用上一道题一样的字母替换法),检索完毕后将新的路径装到vector后面,然后再把vector入列,直到找到所有最短路径。
不过这样是很容易TLE的。我们需要一个currentlen变量来记录当前走的步数以及minlen变量来记录最短路径的长度。设想一种中间情况我们处理的vector容量已经到4了,也就是现在已经走了4步了,currentlen为4,那么我们在走第5步的时候发现了终点,minlen于是更新为5,此时队列的vector容量分别有4(正在处理这个),4,4,5,5,5,5……,在已知最短路径为5 的情况下,我们只需要把接下来两个4处理了看还能不能找到最短路径即可,后面的5就不用处理了,因为就算能到终点也不是最短路径了,这就是剪枝。
还有,上一道题我们每走一步,就从原图中把这一步给删除掉,而这道题要求的是最短路径的集合,如图:
如果在检索红4能走的路径的时候,走到蓝5然后把蓝5删掉了,那么接下来黄4这条路就断了,所以我们在4的话就必须等所有的4都处理完才能删除走过的节点,也就是这个蓝5。
操作:
都在代码注释了。
2. 代码
class Solution {
public:
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
int currentlen = 1, minlen = INT_MAX;//前者是当前探查的路径长度,后者是已知的最短路径长度。
vector<vector<string>> result;
queue<vector<string>> paths;//存放所有的路径
vector<string> p{ beginWord };//所有路径的起始点
paths.push(p);
set<string> wordlist(wordList.begin(), wordList.end());
set<string> chongfuwords;//记录每次新添加的节点,这些节点在新的一轮路径搜索中要从原图去掉。
while (!paths.empty())
{
auto currentpath = paths.front();
paths.pop();
if (currentpath.size() > currentlen) {//如果大于,说明现在探查已经进入“下一轮”了
for (string str : chongfuwords) {//这个时候才是利用完毕新节点,把它们从原图删掉。防止路径探查往回走,出现死循环
wordlist.erase(str);
}
chongfuwords.clear();//清空上一轮的节点,准备下一轮。
currentlen = currentpath.size();//更新此轮的路径长度
if (currentlen >= minlen)
break;//如果此轮的路径长度已经大于等于最短路径,那么以后每一轮的长度都只会比最短路径更大,所以不用再检索了,游戏结束
}
string lastnode = currentpath.back();//取出当前路径最后的一个节点,开始检索这个节点后面存在的路径
for (int i = 0; i< lastnode.size(); i++)
{
string str = lastnode;//注意每一轮i循环都是下标跳到下一位。要重新给str赋值成原单词
for (int j = 0; j < 26; j++)
{
str[i] = 'a' + j;//每次修改一位,将这一位从a到z遍历,修改完后的单词保存在str
if (wordlist.find(str) != wordlist.end()) {
vector<string> newpath = currentpath;//新的路径得从上一个路径继承,然后尾部添加一个新的节点
newpath.push_back(str);
if (str == endWord) {//如果恰好尾部节点是终点,就可以添加到结果容器中。
result.push_back(newpath);
minlen = currentlen;//由于是BFS,所以第一次更新这个值的时候肯定就是最短路径。
} else {
paths.push(newpath);//如果不是终点,则还需添加到路径队列中继续搜索。
}
chongfuwords.insert(str);
//wordlist.erase(str);//注意:这里不能像上一道题目那样把新找到的节点用一次就删掉,因为后面可能的最短路径也要用到这个节点。
}
}
}
}
return result;
}
};