WordLadder I
Given two words (beginWord and endWord), and a dictionary’s word list,
find the length of shortest transformation sequence from beginWord to
endWord, such that:Only one letter can be changed at a time Each intermediate word must
exist in the word list For example,Given: beginWord = “hit” endWord = “cog” wordList =
[“hot”,”dot”,”dog”,”lot”,”log”] As one shortest transformation is
“hit” -> “hot” -> “dot” -> “dog” -> “cog”, return its length 5.Note: Return 0 if there is no such transformation sequence. All words
have the same length. All words contain only lowercase alphabetic
characters.
题目比较简单,基本可以看做最短路径问题。Word Ladder I求最短路径的径长,Word Ladder II找出所有的最短路径。
对于寻找最短路径的长度,通过常规的BFS就可以解决。因为BFS实际上是逐层遍历的,这样先达到终点的这条路径就是最短的。
除此之外,还要注意一般情况下,字典的中键的长度比较短,但是数目往往都是很多的,并且键是放在哈希表中,理想情况下查找可以视为O(1)。所以我们抵达一个单词,比如hit,对每一位设置所有可能的26个字母,并去查找是否在哈希表中,每一个层级的复杂度可以视为O(26n) = O(n),n为键长。
WordLadder II
这道题目,需要打印出全部最短路径。如果采用回溯的方法,使用dfs暴力搜索,无论怎样剪枝都会TLE。
所以还是要采用BFS的方法,同时也需要剪枝才能满足时间要求。
BFS找最短路径在WordLadder I中已经提到过,接下来有两个问题:
1、使用BFS打印出全部最短路径。
2、针对BFS剪枝优化。
使用BFS打印出全部最短路径
首先,如果在BFS的过程中打印经过的最短路径?BFS不像DFS,DFS在递归的过程中可以借助递归栈回溯到根节点。在BFS中,保存某个点经过的路径,需要对每个点构造邻接表。
在本题中,通过BFS可以遍历到最后的终点如上图。如果要打印出所有路径,实际上是要从后往前打印,因为只有遍历到终点,才知道这是一条可达路径,这和DFS相同。
打印的方法就是反向建立邻接表,如下图:
邻接表,可以使用哈希表,在每个节点存储一个链表来实现。这样我们从cog开始,利用dfs即可打印到最后。方法如下:
private void buildPath(String curWord, String start, List<String> path, List<List<String>> results, Map<String, List<String>> map) {
//map就是每个节点存储的邻接表。
if (curWord.equals(start)) {
path.add(0,start);
results.add(new ArrayList<>(path));
path.remove(0);
return;
}
path.add(0,curWord);
if (map.containsKey(curWord)) {
for (String nextWord : map.get(curWord)) {
buildPath(nextWord,start,path,results,map);
}
}
path.remove(0);
}
针对BFS剪枝优化
接下来,因为对时间要求十分严格,所以还需要对BFS剪枝。最短路径问题,BFS加上剪枝,其实就可以想到Dijkstra算法了。
在每个节点,记录根节点到当前节点的最短距离dis,同样可以采用map数据结构。
当从一个节点可以抵达一个相邻点时,首先计算step = 当前节点的dis +1,与目的点的dis进行对比,如果step>dis,那么说明当前点到下一点所构成的路径肯定比到下一点的其它最短路径长,所以不可能构成一条新的最短路径,直接pass掉这种可能,寻找其它可能。达到剪枝的目的,同样也就是Dijkstra算法的松弛操作。
整体BFS中,基本就分为两步:
1、松弛操作
2、构建邻接表
代码如下:
Queue<String> queue= new ArrayDeque<String>();
queue.add(start);
map = new HashMap<>();
Map<String, Integer> ladder = new HashMap<>();
dict.add(end);
for (String str :dict) ladder.put(str, Integer.MAX_VALUE);
//ladder,记录每一点,根节点当其的最短距离。
ladder.put(start, 0);
while (!queue.isEmpty()) {
String cur = queue.poll();
int nextStep = ladder.get(cur) + 1;
if (nextStep > min) break;
for (int i =0;i<cur.length();i++) {
StringBuilder builder = new StringBuilder(cur);
for (char ch='a';ch<='z';ch++) {
builder.setCharAt(i,ch);
String nextWord = builder.toString();
if (ladder.containsKey(nextWord)) {
int dis = ladder.get(nextWord);
//松弛操作。
//等于的时候,表明目的点已经在队列了,不加到队列里,防止多余的遍历。
if (nextStep > dis) continue;
else if (nextStep < dis) {
queue.add(nextWord);
ladder.put(nextWord, nextStep);
}
//构建邻接表。
if (map.containsKey(nextWord)) {
map.get(nextWord).add(cur);
}else {
List<String> adjacent = new ArrayList<>();
adjacent.add(cur);
map.put(nextWord, adjacent);
}
if (nextWord.equals(end)) min = nextStep;
}
}
}
}
完整代码如下:
public class Solution {
public List<List<String>> findLadders(String start, String end, Set<String> dict) {
Map<String,List<String>> map;
List<List<String>> results;
results= new ArrayList<List<String>>();
if (dict.size() == 0)
return results;
int min=Integer.MAX_VALUE;
Queue<String> queue= new ArrayDeque<String>();
queue.add(start);
map = new HashMap<>();
Map<String, Integer> ladder = new HashMap<>();
dict.add(end);
for (String str :dict) ladder.put(str, Integer.MAX_VALUE);
ladder.put(start, 0);
while (!queue.isEmpty()) {
String cur = queue.poll();
int nextStep = ladder.get(cur) + 1;
if (nextStep > min) break;
for (int i =0;i<cur.length();i++) {
StringBuilder builder = new StringBuilder(cur);
for (char ch='a';ch<='z';ch++) {
builder.setCharAt(i,ch);
String nextWord = builder.toString();
if (ladder.containsKey(nextWord)) {
int dis = ladder.get(nextWord);
if (nextStep > dis) continue;
else if (nextStep < dis) {
queue.add(nextWord);
ladder.put(nextWord, nextStep);
}
//等于的时候,不加到队列里,防止多余的遍历。
if (map.containsKey(nextWord)) {
map.get(nextWord).add(cur);
}else {
List<String> adjacent = new ArrayList<>();
adjacent.add(cur);
map.put(nextWord, adjacent);
}
if (nextWord.equals(end)) min = nextStep;
}
}
}
}
LinkedList<String> path = new LinkedList<>();
buildPath(end, start, path,results,map);
System.out.print(results);
return results;
}
private void buildPath(String curWord, String start, List<String> path, List<List<String>> results, Map<String, List<String>> map) {
if (curWord.equals(start)) {
path.add(0,start);
results.add(new ArrayList<>(path));
path.remove(0);
return;
}
path.add(0,curWord);
if (map.containsKey(curWord)) {
for (String nextWord : map.get(curWord)) {
buildPath(nextWord,start,path,results,map);
}
}
path.remove(0);
}
}