# Question

Formatted question description: https://leetcode.ca/all/126.html

126. Word Ladder II

Given two words (beginWord and endWord), and a dictionary's word list,
find all shortest transformation sequence(s) from beginWord to endWord, such that:

Only one letter can be changed at a time
Each intermediate word must exist in the word list

Example 1:

Input:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
Output:
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]

Example 2:

Input:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
Output: []

Explanation: The endWord "cog" is not in wordList, therefore no possible transformation.

@tag-backtracking



# Algorithm

Because we want to record the path, we need to record the parent node for each node, so that at the end, we will find its parent node one by one from the end point, until we find the starting point, and then flip it and add it to the result set.

Probably the process is similar, but the difference is that when deleting a string in the dictionary, this character may be used in another path. It is like this:

A -> C -> D, B -> C -> D

They will all go through C, and both are the shortest paths. When A is searched for C, and C is deleted from the dictionary. When B is searching for a string with a distance of 1, C is no longer in the dictionary.

So what to do?

We set up a hash table to store a set of parent nodes of a string, so that C is not in the dictionary and then check the hash table to see if C is in the hash table, if it is, and the parent node level of C Same as B, then B is also added to the parent node combination of C. It can be known that the distance of the parent node set of a string from the starting point must be equal, that is, they are the shortest distance.

Finally, after traversing all the points, use DFS to find all sets from the end point forward.

# Code

Java

• import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;

// iteration bfs
public class Solution {

List<List<String>> list = new ArrayList<List<String>>();

public List<List<String>> findLadders(String start, String end, Set<String> dict) {

if (start == null || end == null || dict == null) return list;

int level = 1;
int currentLevelCount = 1;
int newLevelCount = 0;
boolean found = false;
int foundLevel = -1;

// from end word, to all paths
HashMap<String, ArrayList<ArrayList<String>>> hm = new HashMap<String, ArrayList<ArrayList<String>>>();

q.offer(start);
ArrayList<String> singlePath = new ArrayList<String>();
ArrayList<ArrayList<String>> allPaths = new ArrayList<ArrayList<String>>();

hm.put(start, allPaths);

while (!q.isEmpty()) {

String current = q.poll();
currentLevelCount--; // 这里用了新旧count来标记每个level，没有用null

for (int i = 0; i < current.length(); i++) {
char[] array = current.toCharArray();

for (char c = 'a'; c <= 'z'; c++) {
array[i] = c;
String each = new String(array);
if (each.equals(end)) {
found = true;
foundLevel = level;
}

if (dict.contains(each)) {
// q.offer(each);
newLevelCount++;

ArrayList<ArrayList<String>> prevAllPaths = hm.get(current);

if (hm.containsKey(each))
allPaths = hm.get(each);
else {
/* @note@note:
enqueue is here. if no path ending at this one, then has to explore in future
if there is path ending at this one, meaning it's been explored already. no need to enqueue
*/
q.offer(each);
allPaths = new ArrayList<ArrayList<String>>();
hm.put(each, allPaths);
}

// @note@note: this if is the key !!! no path for new word, or new word path is one more than previous path
// using this if, the"if visited" check can be removed
// if (allPaths.size() == 0 || prevAllPaths.size() + 1 == allPaths.size()) {
if (allPaths.size() == 0 || prevAllPaths.get(0).size() + 1 == allPaths.get(0).size()) {
for (ArrayList<String> eachPath : prevAllPaths) {
ArrayList<String> newone = new ArrayList<String>(eachPath);
}
}
}
}
}

// @note@note: also the key, to make sure only find shortest
if (found && foundLevel != level) {
break;
}

// @note: must be after trying the last word of currentLevel, then update
if (currentLevelCount == 0) {
currentLevelCount = newLevelCount;
newLevelCount = 0;
level++;

}

}

if (!found) {
return list;
}

for (ArrayList<String> each : hm.get(end)) {
}

return list;

}
}

public class Solution_recursion {

private List<String> findPath(String fromWord, String toWord,
Set<String> seenWords) {

if (fromWord.equals(toWord)) {
ArrayList<String> result = new ArrayList<>();
return result;
}

// Find all words that you can go to from fromWord
List<String> nextWords = getNextWords(fromWord, seenWords);
for (String word : nextWords) {
Set<String> newSeenWords = new HashSet<String>(seenWords);
List<String> subPath = findPath(word, toWord, newSeenWords);
if (subPath != null) {
return subPath;
}
}

// There wasn't a path
return null;

}

private final List<String> WORDS = Arrays.asList("head", "heal",
"teal", "tell", "tall", "tail");

private final List<Character> ALPHA = Arrays.asList('a', 'b',
'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');

private final HashSet<String> DICTIONARY = new HashSet<String>(
WORDS);

private List<String> getNextWords(String fromWord,
Set<String> seenWords) {
List<String> outList = new ArrayList<String>();
StringBuilder builder;
for (int i = 0; i < fromWord.length(); i++) {
builder = new StringBuilder(fromWord);
for (Character j : ALPHA) {
if (j == fromWord.charAt(i)) {
continue;
}
builder.setCharAt(i, j);
String potentialWord = builder.toString();
if (DICTIONARY.contains(potentialWord)
&& !seenWords.contains(potentialWord)) {
}
}
}
return outList;
}
}
}

• Todo

• from collections import deque

class Solution(object):
"""
:type beginWord: str
:type endWord: str
:type wordlist: Set[str]
:rtype: List[List[int]]
"""

def getNbrs(src, dest, wordList):
res = []
for c in string.ascii_lowercase:
for i in range(0, len(src)):
newWord = src[:i] + c + src[i + 1:]
if newWord == src:
continue
if newWord in wordList or newWord == dest:
yield newWord

def bfs(beginWord, endWord, wordList):
distance = {beginWord: 0}
queue = deque([beginWord])
length = 0
while queue:
length += 1
for k in range(0, len(queue)):
top = queue.popleft()
for nbr in getNbrs(top, endWord, wordList):
if nbr not in distance:
distance[nbr] = distance[top] + 1
queue.append(nbr)
return distance

def dfs(beginWord, endWord, wordList, path, res, distance):
if beginWord == endWord:
res.append(path + [])
return

for nbr in getNbrs(beginWord, endWord, wordList):
if distance.get(nbr, -2) + 1 == distance[beginWord]:
path.append(nbr)
dfs(nbr, endWord, wordList, path, res, distance)
path.pop()

res = []
distance = bfs(endWord, beginWord, wordlist)
dfs(beginWord, endWord, wordlist, [beginWord], res, distance)
return res