LeetCode – Word Ladder

Given two words (start and end), and a dictionary, find the length of shortest transformation sequence from start to end, such that only one letter can be changed at a time and each intermediate word must exist in the dictionary. For example, given:

start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]

One shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog", the program should return its length 5.

Analysis

UPDATED on 06/07/2015.

So we quickly realize that this is a search problem, and breath-first search guarantees the optimal solution.

word-ladder

Java Solution

class WordNode{
    String word;
    int numSteps;
 
    public WordNode(String word, int numSteps){
        this.word = word;
        this.numSteps = numSteps;
    }
}
 
public class Solution {
    public int ladderLength(String beginWord, String endWord, Set<String> wordDict) {
        LinkedList<WordNode> queue = new LinkedList<WordNode>();
        queue.add(new WordNode(beginWord, 1));
 
        wordDict.add(endWord);
 
        while(!queue.isEmpty()){
            WordNode top = queue.remove();
            String word = top.word;
 
            if(word.equals(endWord)){
                return top.numSteps;
            }
 
            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(wordDict.contains(newWord)){
                        queue.add(new WordNode(newWord, top.numSteps+1));
                        wordDict.remove(newWord);
                    }
 
                    arr[i]=temp;
                }
            }
        }
 
        return 0;
    }
}
Category >> Algorithms  
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>
  • Steve Dyson

    any c# code?

  • Steve Dyson

    this is not working, returns 0

  • Sohrab Ahmad

    for the same input as yours with dictionary changed to below:

    dict = [“hot”,”dot”,”dog”,”lot”,”aog”,”log”]

    answer will change like : “hit” -> “hot” -> “dot” -> “dog” -> “aog”->”cog”,

    but from dog to cog also we can go directly, how will you avoid that?

  • Eugene Arnatovich

    Solution just using 2 HashMaps and 1 ArrayList

    package leetcode;

    import java.util.ArrayList;

    import java.util.HashMap;

    import java.util.List;

    import java.util.Map;

    public class Solution {

    private static int min = Integer.MAX_VALUE;

    private static List visitedkeys = new ArrayList();

    public void wordladder(String start, String end, String[] dict) {

    //Word ladder

    Map map2 = new HashMap();

    Map<String, List> map = new HashMap();

    int i=0;

    map2.put(i++, start);

    map.put(start, new ArrayList());

    for (String d : dict) {

    map2.put(i++, d);

    map.put(d, new ArrayList());

    }

    map2.put(i, end);

    map.put(end, new ArrayList());

    int n = map2.size();

    for (i=0; i < n; i++) {

    for (int j=0; j < n; j++) {

    if (isConvertible(map2.get(i), map2.get(j))) {

    map.get(map2.get(i)).add(map2.get(j));

    }

    }

    }

    length(map, start, end, 1);

    if (min != Integer.MAX_VALUE){

    System.out.println(min);

    }

    else {

    System.out.println("no path");

    }

    }

    private boolean isConvertible(String s1, String s2) {

    if (s1.length() != s2.length()) {

    return false;

    }

    int diff = 0;

    for (int i = 0; i < s1.length(); i++) {

    if (s1.charAt(i) != s2.charAt(i)) {

    diff += 1;

    }

    }

    return (diff == 1) ? true : false;

    }

    private void length(Map<String, List> map, String key, String end, int len) {

    if (key == end) {

    if (len < min)

    min = len;

    return;

    }

    List lst = map.get(key);

    for (String v : lst) {

    if (!visitedkeys.contains(v)){

    visitedkeys.add(v);

    length(map, v, end, len+1);

    visitedkeys.remove(visitedkeys.size()-1);

    }

    }

    }

    }

  • Dhanaraj D

    Does this solution give length of shortest transformation sequence or length of first sequence found ?

  • Surbhi Motghare

    Can you please tell me the complexity for both the methods?

  • subash sethy

    can you tell me what is the complexity of this method

  • Roman

    Solution posted in the task is not correct.

    Here is mine:

    Test.java file:

    import java.util.*;
    public class Test {
    public static void main(String argv[]){
    String start = "hit";
    String end = "cog";
    String[] dict = {"cot","hot","dot","cit","dog","lot","log"};
    // "hit" -> "cit" -> "cot" -> "cog"
    // length = 4
    int minDistance = getDistance(start, end);
    List nodesToCheck = new ArrayList();
    List newNodes = new ArrayList();
    List winners = new ArrayList();
    nodesToCheck.add(new TreeElem(start, null, minDistance, 1));
    do {
    for (TreeElem node : nodesToCheck) {
    for (int i = 0; i 0);
    if (winners.size() == 0) {
    System.out.println("No solution found.");
    return;
    }
    TreeElem winner = null;
    int minLength = -1;

    for (TreeElem e : winners) {
    if (winner == null) {
    winner = e;
    minLength = e.distanceFromTop;
    }

    if (e.distanceFromTop = 0; i--) {
    System.out.print("[" + steps[i] + "] -> ");
    }
    System.out.println("[" + end + "]");
    }
    public static int getDistance(String str1, String str2) {
    int dist = 0;
    for (int i = 0; i < str1.length(); i++) {
    if (str1.charAt(i) != str2.charAt(i)) {
    dist++;
    }
    }
    return dist;
    }
    public static boolean isUsed(TreeElem node, String value) {
    while (node.parent != null) {
    if (node.value.equals(value)) {
    return true;
    }
    node = node.parent;
    }
    return false;
    }
    }

    TreeElem.java file:


    import java.util.*;
    public class TreeElem{
    public String value;
    public TreeElem parent;
    List childs;
    public int distanceToEnd;
    public int distanceFromTop;
    public TreeElem(String value, TreeElem parent, int distanceToEnd, int distanceFromTop) {
    this.value = value;
    this.parent = parent;
    this.childs = new ArrayList();
    this.distanceToEnd = distanceToEnd;
    this.distanceFromTop = distanceFromTop;
    }
    public void addChild(TreeElem child) {
    childs.add(child);
    }
    }

  • CRH

    Without using a queue. Please let me know if you see any issues with this?

    public int getCount(String start, String end, Set dict){

    // Basic edge cases

    if(start==null || end==null || start.length()!=end.length()) return -1;

    if(dict.isEmpty()) return -1;

    dict.add(end);

    int count = 0;

    int i = 0;

    while(i<start.length()){

    boolean found = false;

    char[] arr = start.toCharArray();

    char tmp = arr[i];

    for(int j=97;j<=122;j++){

    arr[i] = (char)j;

    String checker = new String(arr);

    if(dict.contains(checker)){

    count++;

    dict.remove(checker);

    if(checker.equals(end)) {

    return count;

    }

    else {

    start = checker;

    found = true;

    i = 0;

    break;

    }

    }

    }

    if(!found){

    arr[i] = tmp;

    i++;

    }

    }

    return -1;

    }

  • Can be solved by Dijkstra.

  • Too much code for an interview or competition problem.
    Try to keep it as simple and small as possible 😉

  • BFS is a really good way of solving this problem

  • Dado

    Hi
    I have solved this problem using Graph of word and then run on it BFS.

    package Questions;

    import java.util.HashMap;
    import java.util.Queue;
    import java.util.HashSet;
    import java.util.LinkedList;

    public class WordLadder {

    //solution function
    public static int ladderLength(String start, String end,
    HashSet dict) {
    // saving the graph nodes on hash map.
    HashMap nodes = new HashMap();
    // adding the start and the end words to the dict
    dict.add(start);
    dict.add(end);
    for (String word : dict) {
    nodes.put(word, new GraphNode(word));
    }
    // update each node’s adjacents according to one character different relation
    Object[] dictArray = dict.toArray();
    for (int i = 0; i < dictArray.length; i++) {
    for (int j = i + 1; j < dictArray.length; j++) {
    if (isNeighbor((String) dictArray[i], (String) dictArray[j])) {
    nodes.get((String) dictArray[i]).childs.add(nodes
    .get((String) dictArray[j]));
    nodes.get((String) dictArray[j]).childs.add(nodes
    .get((String) dictArray[i]));
    }
    }
    }

    // Run BFS on the Graph and take the dist generated as result
    HashMap result = BFS(nodes, start);

    // Return the distance of the end word node from the start word node
    return result.get(end);

    }

    // BFS function
    public static HashMap BFS(
    HashMap nodes, String start) {

    HashMap visited = new HashMap();
    HashMap dist = new HashMap();
    for (String key : nodes.keySet()) {
    visited.put(key, 0);
    dist.put(key, 0);
    }
    Queue q = new LinkedList();
    q.add(start);
    visited.put(start, 1);
    while (!q.isEmpty()) {
    String dequeued = q.remove();
    GraphNode curNode = nodes.get(dequeued);
    LinkedList currAdjs = curNode.childs;
    for (int i = 0; i < currAdjs.size(); i++) {
    GraphNode adj = (GraphNode) currAdjs.get(i);
    if (visited.get(adj.word) == 0) {
    visited.put(adj.word, 1);
    dist.put(adj.word, dist.get(dequeued) + 1);
    q.add(adj.word);
    }

    }
    }
    return dist;

    }

    // check if two words differ by one character
    public static boolean isNeighbor(String a, String b) {
    assert a.length() == b.length();
    int differ = 0;
    for (int i = 0; i 1)
    return false;
    }
    return true;
    }

    public static void main(String[] args) {
    // dict = [“hot”,”dot”,”dog”,”lot”,”log”] result 5;
    HashSet dict = new HashSet();
    dict.add(“hot”);
    dict.add(“dot”);
    dict.add(“dog”);
    dict.add(“lot”);
    dict.add(“log”);
    System.out.println(ladderLength(“hit”, “cog”, dict));
    }

    }

    class GraphNode {
    String word;
    LinkedList childs;

    public GraphNode(String word) {
    this.word = word;
    childs = new LinkedList();
    }
    }
    I would like to have feedback on my code ?
    Thanks

  • Stephen Boesch

    That’s nice idea to keep only the actual existing letters at each index.

  • Stephen Boesch

    suboptimal solutions. Better would be 1-grams.

  • dwelo

    You also don’t necessarily need two queues. You could create a WordNode class inside the method and have a word field and a distance field. Then you can create a queue of “WordNode”s. Also, I think there is no need to compare the distance to previously recorded one, since breadth search will reach the end word in the shortest path. Whenever you reach the end word, that is the shortest distance.

  • Nooby

    Thanks a ton!! You rock!! Keep it up 🙂

  • ryanlr

    The problem is fixed.

  • ryanlr

    Thanks! Changed. And you are welcome:)

  • zhuoran

    You just need to add end to dict. Then it will be correct.
    Btw, thanks a lot for ur code:)

  • Curious Guy

    You can create a separate list that stores words when found in the dictionary.

  • Cong

    Yes, you are right. Surprisingly, the seoncd one can be acceptted by leetcode.

  • hdante

    shortest path problem with dictionary extended to include start and end and weight equals to 1 if distance(a, b) == 1, else weight equals infinite

  • BK

    we stop at the first time we find the target word and return “length”. All the words that will be added in the stack after that will be > length as we push words in stack after length +1 .

  • BK

    We can add a condition that if (newword.equals(target)) return currDistance +1;

  • dfgd

    yeah second algo is wrong

  • Guanting

    Hi! I think you were oversimplifying the problem. the edge cannot be weighted just based on the number of characters in common. And an edge cannot even be determined between two words just because they have common characters. You have to factor in what are included the dictionary. One simple example, ‘good’ and ‘doom’ have two common characters and suppose the dictionary only contains ‘good’ and ‘doom’…Apparently no edge of path could be established between the two words.

  • ryanlr

    Why? And how does a correct one look like?

  • Shaochen Huang

    Agree with most of the comments here:

    First: the dictionary here needs to include the end word

    Second: the second solution does not find the optimal shortest path, it only finds the first valid path.

    I have come up with my own solution which does the following:

    1. Instead of trying to iterate through all chars from a to z, it parses the dictionary and maintains a map between index and possible chars for that index in the dictionary, so flipping a character only flipped to possible dictionary words.

    2. recursive Graph depth first search which keeps tracks of all sub path size and choose the shortest one.

    Please kindly review and comment to help improve the algo

    https://github.com/ifwonderland/leetcode/blob/master/src/main/java/leetcode/string/WordLadder.java
    https://github.com/ifwonderland/leetcode/blob/master/src/main/java/leetcode/util/WordFlipperUtils.java

  • Yifan Peng

    I think this solution is totally wrong.

  • Sole

    I think there should be a check on the currWord and the end to stop the search in the dict:

    /*if(currWord.equals(end)){
    return currDistance;
    }*/
    if(isLastWord(currWord,end)){
    break;
    }
    where isLastWord is something like this:

    private boolean isLastWord(String current, String end){
    int count = 0;
    boolean isLastWord = true;
    for(int i=0; i1){
    isLastWord = false;
    break;
    }
    }
    }
    return isLastWord;
    }

  • AlgorithmFreak

    The second solution doesn’t work. Try with input: “hit”, “lag”, [“hot”,”hog”,”dot”,”dog”,”lot”,”log”]
    Example sequence: hit > hot > hog > log > lag The answer should be 5. But, it shows 0

  • Tex

    I mean first

  • Tex

    Priest solution is obviously wrong even if don’t consider if it’s optimally

  • ajay

    how it will accepted?? as it will not return as cog is not inside in dict, so currWord.equals(end) will false and return 0.

  • hbrong

    The algorithm can’t guarantee find the shortest transformation sequence.

  • Dun Liu

    Nice!!!

  • Kae Pajunar

    How can I print the world ladder answers, after getting the distance.? Thanks for the answers.

  • jason

    This should be a shortest path problem: if two words has common character, they are connected by edge. The weight of the edge is the number of character in common. So the problem becomes shortest path problem.

  • elvalord

    That’s right. A sentence should be added “dict.add(end)”.

  • 74s0nf

    hey guess for the “a”, “c” dict = {a,b,c} example the correct ans should be 1 😉

  • Csnerds

    Also, “int result = 0;” This sentence seems redundant..

  • Csnerds

    The second algo has a bug. What the problem says is that “Each intermediate word must exist in the dictionary”. However, this solution assumes the last one(“cog”) also need to be in the dict, which is not necessary..