127. Word Ladder && 126. Word Ladder II
127. Word Ladder
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.
public class Solution { public int ladderLength(String beginWord, String endWord, Set<String> wordList) { Set<String> beginSet = new HashSet<>(); beginSet.add(beginWord); Set<String> endSet = new HashSet<>(); endSet.add(endWord); int ladderLen = 1; HashSet<String> visited = new HashSet<>(); //make sure the evolution doesn't go back while (!beginSet.isEmpty() && !endSet.isEmpty()) { //switch begin and end set for better performance //only expand the set with fewer elements if (beginSet.size() > endSet.size()) { Set<String> temp = beginSet; beginSet = endSet; endSet = temp; } Set<String> newBeginSet = new HashSet<>(); for (String word : beginSet) { //analyze each word in beginSet char[] chs = word.toCharArray(); for (int i = 0; i < chs.length; ++i) { char old = chs[i]; //update each char in the word little by little, and add it to newBeginSet. for (char c = 'a'; c <= 'z'; ++c) { chs[i] = c; String target = String.valueOf(chs); if (endSet.contains(target)) return ladderLen + 1; if (!visited.contains(target) && wordList.contains(target)) { newBeginSet.add(target); visited.add(target); } } chs[i] = old; } } beginSet = newBeginSet; ++ladderLen; } return 0; } }
Graph solution, based on problem 126. Word Ladder II. But got a TLE here because doing extra work to get the path. We only need the length in this problem.
public class Solution { public int ladderLength(String beginWord, String endWord, Set<String> wordList) { wordList.add(beginWord); wordList.add(endWord); Map<String, List<String>> neighborsMap = new HashMap<>(); Map<String, Integer> distance = graphShortestPath(neighborsMap, beginWord, wordList); if (neighborsMap.get(endWord) == null) return 0; return distance.get(endWord) + 1; } //Do a breadth first search to get a Distance Map that keep the distance from the node to source private Map<String, Integer> graphShortestPath(Map<String, List<String>> neighborsMap, String start, Set<String> allWords) { Map<String, Integer> distance = new HashMap<>(); distance.put(start, 0); Queue<String> unprocessed = new LinkedList<>(); unprocessed.add(start); while (!unprocessed.isEmpty()) { String current = unprocessed.remove(); List<String> neighbors = expand(current, allWords); neighborsMap.put(current, neighbors); int neighborDist = distance.get(current) + 1; for (String neighbor : neighbors) { if (!distance.containsKey(neighbor)) /*This neighbor is not yet visited*/ { distance.put(neighbor, neighborDist); unprocessed.add(neighbor); } } } return distance; } //return a list of words by just change one letter private List<String> expand(String seed, Set<String> dict) { List<String> expansion = new ArrayList<>(); for (int i = 0; i < seed.length(); ++i) { char orig = seed.charAt(i); for (char ch = 'a'; ch <= 'z'; ++ch) { if (ch != orig) { String expanded = seed.substring(0, i) + ch + seed.substring(i + 1); if (dict.contains(expanded)) expansion.add(expanded); } } } return expansion; } }
126. Word Ladder II
Return the path list.For example,
Given:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
Return
[ ["hit","hot","dot","dog","cog"], ["hit","hot","lot","log","cog"] ]
Solution:
Create a graph based on all words. Connected nodes are those who have only one letter different. So the problem is to find the shortest path from StartWord to EndWord.
public class Solution { public List<List<String>> findLadders(String beginWord, String endWord, Set<String> wordList) { wordList.add(beginWord); wordList.add(endWord); Map<String, List<String>> neighborsMap = new HashMap<>(); Map<String, Integer> distance = graphShortestPath(neighborsMap, beginWord, wordList); List<List<String>> ladders = new ArrayList<>(); if (neighborsMap.get(endWord) == null) return ladders; getPaths(ladders, new ArrayDeque<>(), beginWord, endWord, distance, neighborsMap); return ladders; } //Do a breadth first search to get a Distance Map that keep the distance from the node to source private Map<String, Integer> graphShortestPath(Map<String, List<String>> neighborsMap, String start, Set<String> allWords) { Map<String, Integer> distance = new HashMap<>(); distance.put(start, 0); Queue<String> unprocessed = new LinkedList<>(); unprocessed.add(start); while (!unprocessed.isEmpty()) { String current = unprocessed.remove(); List<String> neighbors = expand(current, allWords); neighborsMap.put(current, neighbors); int neighborDist = distance.get(current) + 1; for (String neighbor : neighbors) { if (!distance.containsKey(neighbor)) /*This neighbor is not yet visited*/ { distance.put(neighbor, neighborDist); unprocessed.add(neighbor); } } } return distance; } //DFS to get all paths private void getPaths(List<List<String>> ladders, Deque<String> current, String START, String end, Map<String, Integer> distance, Map<String, List<String>> neighborsMap) { current.push(end); if (end.equals(START)) { ladders.add(new ArrayList<>(current)); return; } for (String neighbor : neighborsMap.get(end)) { if (distance.get(end) == distance.get(neighbor) + 1)/*Find the parent, not just a neighbor*/ { getPaths(ladders, current, START, neighbor, distance, neighborsMap); current.pop(); } } } //return a list of words by just change one letter private List<String> expand(String seed, Set<String> dict) { List<String> expansion = new ArrayList<>(); for (int i = 0; i < seed.length(); ++i) { char orig = seed.charAt(i); for (char ch = 'a'; ch <= 'z'; ++ch) { if (ch != orig) { String expanded = seed.substring(0, i) + ch + seed.substring(i + 1); if (dict.contains(expanded)) expansion.add(expanded); } } } return expansion; } }