LeetCode – Word Ladder II (Java)

Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from start to end, such that: 1) Only one letter can be changed at a time, 2) Each intermediate word must exist in the dictionary.

For example, given: start = "hit", end = "cog", and dict = ["hot","dot","dog","lot","log"], return:

  [
    ["hit","hot","dot","dog","cog"],
    ["hit","hot","lot","log","cog"]
  ]

Analysis

This is an extension of Word Ladder.

The idea is the same. To track the actual ladder, we need to add a pointer that points to the previous node in the WordNode class.

In addition, the used word can not directly removed from the dictionary. The used word is only removed when steps change.

Java Solution

class WordNode{
    String word;
    int numSteps;
    WordNode pre;
 
    public WordNode(String word, int numSteps, WordNode pre){
        this.word = word;
        this.numSteps = numSteps;
        this.pre = pre;
    }
}
 
public class Solution {
    public List<List<String>> findLadders(String start, String end, Set<String> dict) {
        List<List<String>> result = new ArrayList<List<String>>();
 
        LinkedList<WordNode> queue = new LinkedList<WordNode>();
        queue.add(new WordNode(start, 1, null));
 
        dict.add(end);
 
        int minStep = 0;
 
        HashSet<String> visited = new HashSet<String>();  
        HashSet<String> unvisited = new HashSet<String>();  
        unvisited.addAll(dict);
 
        int preNumSteps = 0;
 
        while(!queue.isEmpty()){
            WordNode top = queue.remove();
            String word = top.word;
            int currNumSteps = top.numSteps;
 
            if(word.equals(end)){
                if(minStep == 0){
                    minStep = top.numSteps;
                }
 
                if(top.numSteps == minStep && minStep !=0){
                    //nothing
                    ArrayList<String> t = new ArrayList<String>();
                    t.add(top.word);
                    while(top.pre !=null){
                        t.add(0, top.pre.word);
                        top = top.pre;
                    }
                    result.add(t);
                    continue;
                }
 
            }
 
            if(preNumSteps < currNumSteps){
                unvisited.removeAll(visited);
            }
 
            preNumSteps = currNumSteps;
 
            char[] arr = word.toCharArray();
            for(int i=0; i<arr.length; i++){
                for(char c='a'; c<='z'; c++){
                    char temp = arr[i];
                    if(arr[i]!=c){
                        arr[i]=c;
                    }
 
                    String newWord = new String(arr);
                    if(unvisited.contains(newWord)){
                        queue.add(new WordNode(newWord, top.numSteps+1, top));
                        visited.add(newWord);
                    }
 
                    arr[i]=temp;
                }
            }
 
 
        }
 
        return result;
    }
}
Category >> Algorithms >> Interview  
If you want someone to read your code, please put the code inside <pre><code> and </code></pre> tags. For example:
<pre><code> 
String foo = "bar";
</code></pre>

  1. Mimanshu on 2015-6-12

    This seems to be wrong. It will only print one path. After that queue will be empty and loop wont run.
    Am I inferring correctly?

  2. Haas Fiona on 2015-7-15

    The code might miss some cases when there are multiple path from start to end with multiple length.

  3. Haas Fiona on 2015-7-15

    I guess I am wrong. With BSF, the shortest path should be found first. Therefore, the first path should be the shorted path. The code is correct.

  4. Jayesh on 2015-10-19

    If anyone interested for both BFS and DFS approach for finding Word Ladder:

    http://javabypatel.blogspot.in/2015/10/word-ladder-doublets-word-links-word-golf.html

  5. Billionaire on 2015-11-21

    Why remove used elements from hashset can boost performance? Isn’t that hashset is O(1) time complexity?

  6. Mohamed Hassan on 2015-12-10

    you must remove used elements to avoid loops

  7. Prakash on 2016-1-25

    No, You are thinking it in with 1 example only .
    Code is correct on single Iteration of nested loop it will create all possible Threads for LinkedList.
    Each thread will return the different path once word match is found.
    Array List will hold all possible path.

  8. Matias SM on 2016-4-2

    I don’t understand why would you need to keep the words in the dictionary (instead of deleting them right away, like in the previous case). If you reach a word again, it means that the result will be necessarily of a longer distance, so it can’t be in the shortest path. For the case of the end word, a simple validation when removing the word from the dict, will do the trick (or a different approach than having it in the dict).

    I believe you should keep processing to get all the shortest path solutions, but filtering should be done at the WordNode level. That is: once you find the shortest distance, you don’t process nodes with greater distance than that. Also, because of the way the “graph” is generated in this case, you know that nodes are queued in increasing distance order, so you can stop processing once the best distance is passed.

  9. Prabhat Meghwal on 2016-10-2

    “BSF” Really :O

  10. Kunal Veera on 2017-2-21


    import java.util.*;
    public class HelloWorld{
    static ArrayList Dict = new ArrayList();
    static String start = "hit";
    static String end = "cog";
    static ArrayList<ArrayList> ans = new ArrayList();
    public static void main(String []args){
    ArrayList ch = new ArrayList();
    Dict.add("hot");
    Dict.add("dot");
    Dict.add("dog");
    Dict.add("lot");
    Dict.add("log");
    findS(start, end, ch);
    System.out.println(ans);
    }

    public static void findS(String s, String e, ArrayList ch){
    char[] sToC = s.toCharArray();
    ch.add(s);
    for(int i=0;i<sToC.length;i++){
    char old=sToC[i];
    for(char c='a'; c<'z';c++){
    sToC[i]=c;
    if((String.valueOf(sToC)).equals(end)){
    ch.add(String.valueOf(sToC));
    System.out.println("Done");
    ans.add(ch);
    return;
    }
    if(Dict.contains(String.valueOf(sToC))){
    if(!ch.contains(String.valueOf(sToC))){
    ArrayList newA = copy(ch);
    System.out.println("Gonna call recurse with " + String.valueOf(sToC));
    findS(String.valueOf(sToC),end,newA);
    }
    }
    }
    sToC[i]=old;
    }
    }

    public static ArrayList copy(ArrayList ch){
    ArrayList newA = new ArrayList();
    for(int i=0;i<ch.size();i++){
    newA.add(ch.get(i));
    }
    return newA;
    }
    }

  11. Jayam on 2017-3-5

    the MinStep & top steps including If conditions are not necessary. I have removed them & all test cases passed.

    ———————————————————————————————————————-
    public static List<List> wordLadderII(String start, String end, List wordList) {
    Set dict = new HashSet(wordList);
    List<List> paths = new ArrayList();
    if (!dict.contains(end)) return paths; // if end not in dict then return

    LinkedList queue = new LinkedList();
    queue.add(new WNode(start, 1, null));// start que with start word & dist as 1

    HashSet visited = new HashSet();
    HashSet unvisited = new HashSet();
    unvisited.addAll(dict);
    int preDist = 0;

    while (!queue.isEmpty()) {
    WNode trav = queue.remove();
    String word = trav.word;
    int currDist = trav.dist;

    // type this after you have typed the word forming logic
    if (word.equals(end)) { // we ahve found
    ArrayList list = new ArrayList();
    list.add(trav.word);
    while (trav.prev != null) {
    list.add(0, trav.prev.word);
    trav = trav.prev;
    }
    paths.add(list);
    continue;
    }

    if (preDist < currDist) { // means we have reached here with the min dist thus there is no point in processsing these words again
    unvisited.removeAll(visited);
    }
    preDist = currDist;

    // new word forming logic
    char[] arr = word.toCharArray();
    for (int i = 0; i < arr.length; i++) {
    for (char c = 'a'; c <= 'z'; c++) {
    char temp = arr[i];
    if (arr[i] != c) {
    arr[i] = c;
    }
    String formed = new String(arr);
    if (unvisited.contains(formed)) {
    queue.add(new WNode(formed, trav.dist + 1, trav));
    visited.add(formed);
    }
    arr[i] = temp;
    }
    }
    }
    return paths;
    }
    ———————————————————————————————————————-

  12. Digvijay Singh Rawat on 2017-3-17

    Yes. Even I am getting only one path. Queue is becoming empty between the final loop.

  13. Digvijay Singh Rawat on 2017-3-17

    Ok. I got the mistake I was making. Instead of doing top.numstep+1, I was doing ++top.numstep which was increasing step for even same step nodes and thus deleting each visited node on comparing prevstep < currstep.

  14. maninblack2013 on 2017-3-25

    I think this program will continue to search until all the words in the dict are removed, which is a huge performance issue. You need to terminate the search after you found all the shortest paths

Leave a comment

*