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 Solution {
 
    public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
        List<List<String>> result = new ArrayList<List<String>>();
 
        HashSet<String> unvisited = new HashSet<>();
        unvisited.addAll(wordList);
 
        LinkedList<Node> queue = new LinkedList<>();
        Node node = new Node(beginWord,0,null);
        queue.offer(node);
 
        int minLen = Integer.MAX_VALUE;
        while(!queue.isEmpty()){
            Node top = queue.poll();
 
            //top if have shorter result already
            if(result.size()>0 && top.depth>minLen){
                return result;
            }
 
            for(int i=0; i<top.word.length(); i++){
                char c = top.word.charAt(i);
                char[] arr = top.word.toCharArray();
                for(char j='z'; j>='a'; j--){
                    if(j==c){
                        continue;
                    }
                    arr[i]=j;
                    String t = new String(arr);
 
                    if(t.equals(endWord)){
                        //add to result
                        List<String> aResult = new ArrayList<>();
                        aResult.add(endWord);
                        Node p = top;
                        while(p!=null){
                            aResult.add(p.word);
                            p = p.prev;
                        }
 
                        Collections.reverse(aResult);
                        result.add(aResult);
 
                        //stop if get shorter result
                        if(top.depth<=minLen){
                            minLen=top.depth;
                        }else{
                            return result;
                        }
                    }
 
                    if(unvisited.contains(t)){
                        Node n=new Node(t,top.depth+1,top);
                        queue.offer(n);
                        unvisited.remove(t);
                    }
                }
            }
        }
 
        return result;
    }
}
 
class Node{
    public String word;
    public int depth;
    public Node prev;
 
    public Node(String word, int depth, Node prev){
        this.word=word;
        this.depth=depth;
        this.prev=prev;
    }
}
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>
  • ryanlr

    True, solution is updated.

  • Rishi Shah

    This code will remove all the previous steps words.

    if(preNumSteps < currNumSteps){
    unvisited.removeAll(visited);
    }

    We need to keep that word in the dict. We can’t remove that word as soon as we used it for the first time. Consider this case [“hit”,”hot”,”dot”,”dog”,”cog”], [“hit”,”hot”,”lot”,”log”,”cog”]. “hot” will come twice. If we will remove this we will unable to fetch the second case. In the previous question we only need length of sequence not the all the possible sequences of the shortest length. That’s why the author of this code is not deleting the repeated words as soon as it occurred.

  • maninblack2013

    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

  • Digvijay Singh Rawat

    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.

  • Digvijay Singh Rawat

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

  • Jayam

    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;
    }
    ———————————————————————————————————————-

  • Kunal Veera


    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;
    }
    }

  • Prabhat Meghwal

    “BSF” Really :O

  • Matias SM

    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.

  • Prakash

    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.

  • Mohamed Hassan

    you must remove used elements to avoid loops

  • Billionaire

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

  • 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

  • Haas Fiona

    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.

  • Haas Fiona

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

  • Mimanshu

    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?