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);
}
}