题目描述
Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from start to end, such that:
Only one letter can be changed at a time
Each intermediate word must exist in the dictionaryFor example,
Given:
start = “hit”
end = “cog”
dict = [“hot”,”dot”,”dog”,”lot”,”log”]Return
[ ["hit","hot","dot","dog","cog"], ["hit","hot","lot","log","cog"] ]
解题思路
- wordladder2 是wordladder的延伸,wordladder是给定起始单词和字典,然后寻找一条最短路径也可能没有此路径,wordladder2是找到所有的最短路径。
- 寻找最短路径利用广度优先的策略,也就是BFS搜素,与其对应的是深度优先搜索DFS,这里广优先利用辅助队列来实现,通常对图、树的数据结构进行BFS和DFS。
- wordladder只需寻找最短路径数值,我们不需要记录路径。当然wordladder实现的时候需要注意利用题目条件,26个小写字母的限制,而非传统比较字符串判断是否可达,这样才能满足题目要求的复杂度。
- wordladder2需要增加路径记录,我们在找最短路径数值的时候每加入到队列中一个点我们会在字典中把这个点删去,这样不影响最短路径数值的记录,但是我们需要求解所有最短路径就不能这样做了,我们需要将每一层(广度优先可以理解为一层一层深入)的节点全部加入的时候才可以将这些节点从集合中一次性删除,这里我们对节点进行了封装,需要记录其前驱节点,同时要记录该节点所处的层次,这样我们才知道当前节点所处层次信息以及某一次层次的遍历情况。
- 这里我第一次超时了,后来进行修正,添加了一个记录当前层次遍历过的节点队列,在加入队列的时候
如果是这些节点数值则跳过不加入,按照先前的思路这些节点会被加入,但是其层次属性会加1,实际上队列中遍历到这些节点的时候,这些节点的数值已经从字典中删除了,会直接略过,如果画图的话会发现这是一些回路情况,这样许多重复节点(数值一样,层次不同)加入队列,很影响效率然后我就用存储当前层次所处理(从队列中弹出并对可达节点分析)过的节点,这样就会将一些回路节点跳过,一定程度上提高效率,然后AC通过。 - 计算路径的时候注意list的插入操作,LinkedList头结点插入,可以在最短时间内得到路径,如果适用ArrayList估计就TLE了。
详细代码
public class Solution {
public List<List<String>> findLadders(String start, String end, Set<String> dict) {
List<List<String>> resultLists = new ArrayList<>();//结果
dict.add(start);//头部加入
dict.add(end);//尾部加入
Queue<NodeForLadder> queue = new LinkedList<NodeForLadder>();
queue.add(new NodeForLadder(start, 1, null));//入队 深度1 父节点null
Set<String> visitedPerDeepList = new HashSet();//记录当前层次遍历过的节点
int currentDeep = 1; //初始化当前深度1
Boolean hasFound = false;
while(!queue.isEmpty())
{
NodeForLadder current = queue.poll();
if(current.deep != currentDeep)
{
//已经进入到下一个层次,将原先层次所有的节点先删除掉
dict.removeAll(visitedPerDeepList);
//更新层次信息
currentDeep = current.deep;
if(hasFound)
{
break;//已经找到最短路径,再次更新层次进入下一层,说明上一层遍历完毕,最短路径们都已走过
}
visitedPerDeepList.clear();//已经遍历的层次节点清空
}
visitedPerDeepList.add(current.string);//加入遍历过的层次节点
if(current.string.equals(end))
{
//找到最短路径 添加
resultLists.add(findLadderPath(current));
hasFound = true; // 抵达最短层
}
else
{
//取点 找临街的表,这里借助最大固定数目的变数来计算而非常规利用矩阵
String tmp = current.string;
//首先判断是否还有此点,以为层次更新时会删除上一层节点,这里排除回路的情况
if(!dict.contains(tmp))
{
continue;
}
for(int i=0;i<tmp.length();i++)
{
for(char c='a';c<='z';c++)
{
if(c==tmp.charAt(i))
{
continue;
}
else
{
char cc[] = tmp.toCharArray();
cc[i] = c;
String s = new String(cc);
if(dict.contains(s) && !visitedPerDeepList.contains(s))//该层的结点访问过的不要再加入队列
{
queue.add(new NodeForLadder(s, current.deep+1, current));
// dict.remove(s);//这里还不能remove 和找最短路径数值不同
}
}
}
}
}
}
return resultLists;
}
class NodeForLadder
{
String string;
int deep;
NodeForLadder parent;//父节点
public NodeForLadder(String string , int deep, NodeForLadder p)
{
this.string = string;
this.deep = deep;
this.parent = p;
}
}
//从根节点依次遍历父节点找到路径 利用链表头部插入操作
List<String> findLadderPath(NodeForLadder n)
{
List<String> pathList = new LinkedList<>();
while(n != null)
{
pathList.add(0, n.string);
n = n.parent;
}
return pathList;
}
}
总结
代码中的注释就是按照上述思路来的,此题属于AC率较低的(前五),尤其适用java来做的话对算法的要求更高,需要熟悉java集合的使用,比如头结点插入操作LinkedList效率高于ArrayList,判断集合中是否含有某一元素的话Set(O(1))效率高于LinkedList(O(n)),许多细节需要注意,总体的思路要正确(利用26小写字母)以及具体编码实现(集合选取,前驱记录,visitedPerDeepList记录等等)