我的LeetCode代码仓:https://github.com/617076674/LeetCode
原题链接:https://leetcode-cn.com/problems/word-ladder-ii/description/
题目描述:
知识点:图的广度优先遍历、图的深度优先遍历、SPFA算法、回溯
思路一:图的广度优先遍历+图的深度优先遍历(LeetCode中提交会超时)
本题是LeetCode127——单词接龙的加强版。
(1)先广度优先遍历,求出endWord的所有前驱结点,以及前驱结点的前驱结点,直到beginWord。
在广度优先遍历时,由于要求得一个结点的所有前驱结点,因此一个结点的前驱结点是一个List列表而不是仅仅只有一个结点。这样的要求使得我们的广度优先遍历算法不能照抄LeetCode127——单词接龙的算法。这个过程中我们需要注意如下图所示的一个问题:
在我们题127的广度优先遍历算法中,如果此时while循环中的队首元素是A,那么A的后继结点C和D将会被标记成已经访问过并入队。当B成为队首元素出队的时候,由于C和D已经被标记访问过了,我们不会再去访问C和D结点,因此我们也就无法找到C和D的前驱结点B,即题127中的方法使得我们对于每一个结点只能确定一个前驱结点,而不能确定一个前驱结点的List。
那么这个问题怎么解决呢?
上述问题出现的原因在于,对于A结点和B结点,它们都属于同一层,在同一层元素还没有全部出队列的时候我们就设置了下一层的元素C和D是否被访问这个属性,使得我们对于C和D结点,只能找到前驱结点A却找不到其前驱结点B。解决的方法其实很简单:我们只需要在每一层的元素都出队之后再来设定其后继结点是否被访问这个属性值即可。
(2)再深度优先遍历这些前驱结点,得到所有的路径。
深度优先遍历的过程需要使用递归来实现,值得注意的是,在递归的过程中遇到要递归的情况,我们都需要新建一个List,而不能所有的递归情况都共用一个List。这是很简单的一个原理,如下图所示:
从a到d这个图中存在两条最短路径a->b->d和a->c->d,我们就需要新建两个List。
具体实现步骤:
a:设置一个函数hasPah()用来判断两个字符串之间是否能相互转换,即图中两个结点是否存在着路径。
b:在findLadders()函数里,新建一个List<List<String>>类型的变量retListList用来记录要返回的值。
c:我们首先判断endWord是否包含在wordList中。如果endWord没有包含在wordList中,我们直接返回retListList。
d:新建一个HashMap<String, List<String>>类型的变量from,用以保存每个结点的前驱结点。新建一个List<String>类型的变量visited,用以记录该结点是否已经被访问。
e:在LeetCode127——单词接龙中,对于图的实现我用的是邻接矩阵的形式,考虑到本题很容易超时,我们用邻接表的形式来实现图。新建一个HashMap<Integer, List<Integer>>类型的变量nextWords,用来记录各个结点是否相连。由于本题和LeetCode127一样是一个无向图,因此在双重循环遍历的时候我们也可以做一些小小的优化,在设置i结点与j结点相连的同时也可以设置j结点与i结点相连,因此在第二重循环遍历时我们可以只遍历比i值要大的那些结点。
f:新建一个Queue<String>类型的队列queue,并把第一个元素beginWord入队,同时在visited中标记beginWord元素已经被访问。
g:只要队列不为空,就进行以下操作循环。
g-1:获得队列中的元素个数,记录为levelCount变量。新建一个List<String>类型的变量tempVisited用以解决在思路分析中思路一中存在的问题。
g-2:再设置一层内循环,只要levelCount &g