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 dictionary
For example,
Given:
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]
Return
[ ["hit","hot","dot","dog","cog"], ["hit","hot","lot","log","cog"] ]
Note:
- All words have the same length.
- All words contain only lowercase alphabetic characters.
這題做得真累,嘗試了幾種不同的解法,感覺一不小心就會超時了。
這題很明顯和上一題一樣,仍然應該使用BFS,關鍵是要保存所有的最短路徑而非單個最短距離。如何保存呢?這裏我使用了兩種不同的思路。
思路1:自定義一個Node類,裏面保存當前節點的所有前驅,然後再用DFS去從end到start遞推獲得所有路徑。
思路2:使用Set<List<String>>。雖然結果需要的形式爲List<List<String>>,可以先用Set保存各個路徑來達到去重的目的,然後調用容器類的addAll方法即可。
和上一題比較不同的是,當前str爲一個字典詞,且也在map裏時,需要進一步判斷:如果這條路徑的長度和當前所保存的路徑長度相等,那麼也應該把當前路徑加入到路徑集合中。但是這裏不能把這個str再次加入到搜索隊列,因爲這個str已經出現在map中了,說明之前肯定已經搜索到了,在第一次搜索到的時候已經加入到了搜索隊列中。如果再次加入搜索隊列,則會導致重複搜索而超時。
思路1實現:
public class Node {
public int dist;
public String str;
public LinkedList<Node> prev;
public Node(int dist, String str) {
this.dist = dist;
this.str = str;
this.prev = new LinkedList<Node>();
}
public void addPrev(Node pNode) {
prev.add(pNode);
}
}
public List<List<String>> findLadders(String start, String end,
Set<String> dict) {
dict.add(end);
// Key: the dictionary string; Value: Node.
Map<String, Node> map = new HashMap<String, Node>();
Queue<String> queue = new LinkedList<String>();
Node startNode = new Node(1, start);
queue.offer(start);
map.put(start, startNode);
List<List<String>> ret = new ArrayList<List<String>>();
while (!queue.isEmpty()) {
String str = queue.poll();
if (str.equals(end)) {
getPaths(map.get(end), map, new ArrayList<String>(), ret);
return ret;
}
for (int i = 0; i < str.length(); i++) {
for (int j = 0; j < 26; j++) {
char c = (char) ('a' + j);
String newStr = replace(str, i, c);
// If a new word is explored.
if (dict.contains(newStr)) {
if (!map.containsKey(newStr)) {
// Construct a new node.
Node node = map.get(str);
Node newNode = new Node(node.dist + 1, newStr);
newNode.prev = new LinkedList<Node>();
newNode.prev.add(node);
map.put(newStr, newNode);
queue.offer(newStr);
} else {
Node node = map.get(newStr);
Node prevNode = map.get(str);
// Increase the path set.
if (node.dist == prevNode.dist + 1) {
node.addPrev(prevNode);
// queue.offer(newStr); // This will cause TLE.
}
}
}
}
}
}
return ret; // Return an empty set.
}
思路2實現:
public List<List<String>> findLadders(String start, String end,
Set<String> dict) {
dict.add(end);
// Key: the dictionary string; Value: Set<List<String>>.
Map<String, Set<List<String>>> map = new HashMap<String, Set<List<String>>>();
Queue<String> queue = new LinkedList<String>();
List<String> startPath = new ArrayList<String>();
startPath.add(start);
Set<List<String>> startSet = new HashSet<List<String>>();
startSet.add(startPath);
queue.offer(start);
map.put(start, startSet);
List<List<String>> ret = new ArrayList<List<String>>();
while (!queue.isEmpty()) {
String str = queue.poll();
if (str.equals(end)) {
ret.addAll(map.get(end));
return ret;
}
for (int i = 0; i < str.length(); i++) {
for (int j = 0; j < 26; j++) {
// Transform it into another word.
String newStr = replace(str, i, (char) ('a' + j));
// If a new word is explored.
if (dict.contains(newStr)) {
if (!map.containsKey(newStr)) {
// Construct a new path set.
Set<List<String>> prevSet = map.get(str);
Set<List<String>> newSet = new HashSet<List<String>>();
for (List<String> path : prevSet) {
List<String> newPath = new ArrayList<String>(
path);
newPath.add(newStr);
newSet.add(newPath);
}
map.put(newStr, newSet);
queue.offer(newStr);
} else {
Set<List<String>> prevSet = map.get(str);
Set<List<String>> newSet = map.get(newStr);
Iterator<List<String>> prevIt = prevSet.iterator();
Iterator<List<String>> newIt = newSet.iterator();
// Increase the path set.
if (prevIt.next().size() + 1 == newIt.next().size()) {
for (List<String> path : prevSet) {
List<String> newPath = new ArrayList<String>(
path);
newPath.add(newStr);
newSet.add(newPath);
// queue.offer(newStr); // This will cause TLE.
}
}
}
}
}
}
}
return ret; // Return an empty set.
}
// Replace a character at the given index of str, with c.
private String replace(String str, int index, char c) {
StringBuilder sb = new StringBuilder(str);
sb.setCharAt(index, c);
return sb.toString();
}
思路2由於不需要使用DFS去還原重構所求路徑,也不需要自定義類,所以代碼會稍微簡短點。但是頻繁的對中間結果路徑的拷貝導致效率相對偏低,思路一會更快一點。
另外有一段代碼是兩個思路都需要的實現的一個子函數,用於還原路徑。
// Get all the paths by using DFS.
private void getPaths(Node end, Map<String, Node> map,
List<String> curPath, List<List<String>> paths) {
if (end == null) {
paths.add(curPath);
return;
}
curPath.add(0, end.str);
if (!end.prev.isEmpty()) {
for (Node prevNode : end.prev) {
getPaths(prevNode, map, new ArrayList<String>(curPath), paths);
}
} else {
getPaths(null, map, curPath, paths);
}
}