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.
Solution 1:
public List<List<String>> findLadders(String start, String end, Set<String> dict) {
List<List<String>> result = new ArrayList<>();
if(start == null || end == null || start.length()!=end.length()) return result;
int minLen = Integer.MAX_VALUE; // shortest transform sequence length
Map<String, Set<String>> visited = new HashMap<>(); // <nextword, previous_words>
Map<String, Integer> level = new HashMap<>(); // <nextword, level>
Queue<String> queue = new LinkedList<>(); // BFS next word queue
visited.put(start, new HashSet<>()); // start word has no previous words
level.put(start, 1);
queue.offer(start);
while (!queue.isEmpty()) {
String word = queue.poll();
int preLevel = level.get(word);
if (preLevel >= minLen) continue;
char[] chars = word.toCharArray();
for (int i = 0; i < word.length(); i++) {
char old = chars[i];
for (char letter = 'a'; letter <= 'z'; letter++) {
if(letter == old) continue;
chars[i] = letter;
String nextWord = new String(chars);
if(!dict.contains(nextWord)) continue;
// if level doesn't contain next word
if(!level.containsKey(nextWord)) {
Set<String> preWords = new HashSet<>();
preWords.add(word);
visited.put(nextWord, preWords);
level.put(nextWord, preLevel + 1);
queue.add(nextWord);
// if level contains next word and near the start
} else if(level.get(nextWord) > preLevel) {
visited.get(nextWord).add(word);
}
if (nextWord.equals(end)) {
minLen = preLevel + 1;
}
}
chars[i] = old;
}
}
buildPaths(end, visited, new LinkedList<>(), result);
return result;
}
private void buildPaths(String end, Map<String, Set<String>> visited,
List<String> path, List<List<String>> paths) {
path.add(0, end);
Set<String> preWords = visited.get(end);
if(preWords == null) return; // can't transform from start to end
if (preWords.size() == 0) { // reached start word
paths.add(new ArrayList<String>(path));
} else {
for (String pre : preWords) {
buildPaths(pre, visited, path, paths);
}
}
path.remove(0);
}
Solution 2:
private class Node {
String word;
int depth;
public Node(String w, int d) {
word = w;
depth = d;
}
}
public List<List<String>> findLadders(String start, String end, Set<String> dict) {
List<List<String>> paths = new ArrayList<List<String>>();
if (start == null || end == null || start.length() == 0) return paths;
// maintain a hashmap for visited words
Map<String, List<Node>> visited = new HashMap<String, List<Node>>();
// BFS to find the minimum sequence length
getMinLength(start, end, dict, visited);
// DFS to back trace paths from end to start
buildPaths(end, start, visited, new LinkedList<String>(), paths);
return paths;
}
/**
* Use BFS to find the minimum transformation sequences length from start to
* end. Also store parent nodes from previous level for each visited valid word.
*/
private void getMinLength(String start, String end, Set<String> dict,
Map<String, List<Node>> visited) {
// maintain a queue for words, depth and previous word during BSF
Queue<Node> queue = new LinkedList<Node>();
queue.add(new Node(start, 1));
dict.add(end);
int lastLevel = 0;
while (!queue.isEmpty()) {
Node node = queue.poll();
if (lastLevel > 0 && node.depth >= lastLevel) break;
// find transformable words in next level
for (int i = 0; i < node.word.length(); ++i) {
StringBuilder sb = new StringBuilder(node.word);
char original = sb.charAt(i);
for (char c = 'a'; c <= 'z'; ++c) {
if (c == original) continue;
sb.setCharAt(i, c);
String s = sb.toString();
// if hits end, mark the current depth as the last level
if (s.equals(end) && lastLevel == 0) {
lastLevel = node.depth + 1;
}
if (dict.contains(s) && !s.equals(start)) {
List<Node> pres = visited.get(s);
if (pres == null) {
// enqueue unvisited word
queue.add(new Node(s, node.depth + 1));
pres = new ArrayList<Node>();
visited.put(s, pres);
pres.add(node);
} else if (pres.get(0).depth == node.depth) {
// parent nodes should be in the same level - to
// avoid circle in graph
pres.add(node);
}
}
}
}
}
}
/* Use DFS to back trace all paths from end to start. */
private void buildPaths(String s, String start,
Map<String, List<Node>> visited, List<String> path,
List<List<String>> paths) {
path.add(0, s);
if (s.equals(start)) {
List<String> p = new ArrayList<String>(path);
paths.add(p);
} else {
List<Node> pres = visited.get(s);
if (pres != null) {
for (Node pre : pres) {
buildPaths(pre.word, start, visited, path, paths);
}
}
}
path.remove(0);
}
Reference:
http://n00tc0d3r.blogspot.com/2013/07/word-ladder-ii.html
http://decomplexify.blogspot.com/2014/05/word-ladder-ii.html