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