Problem: Word Ladder
Difficulty: Medium
Solution: 127_word_ladder.py
The solution has been implemented in Python 2.7.
Word Ladder is a pretty standard problem. You are given a dictionary of words, each of equal length, a begin-word and an end-word. You have to find the minimum number of steps in which you can convert the begin-word into the end-word provided:
- You are only allowed to change one character at a time
- All the intermediate words must be present in the dictionary
To illustrate this, let’s take an example:
In this case, we can transform “hit” to “cog” by the following path:
“hit” -> “hot” -> “dot” -> “dog” -> “cog”
“hit” -> “hot” -> “lot” -> “log” -> “cog”
When I read this problem, the first thing that comes to my mind is Breadth First Search. If we model the dictionary as a graph, where each node is a word and nodes that have only 1 character different are connected together by an edge, then the problem reduces to finding the depth at which we encounter the end-word if we start a Breadth First Search from the begin-word.
In this example, we would start with a queue containing only the begin word. In a loop, we will remove the first element from the queue, and add all it’s children to the queue. The loop exits either when the element popped out of the queue is the end word or the queue is empty. Straightforward BFS.
There are a couple of important points to call out. The first is an optimization – don’t add an element to the queue if it had been added earlier already. Let’s look at the queue for the above example:
A property of the BFS is that all nodes at a particular level are added before nodes at the next level are explored. So if the word “lot” is already in the queue, then it is at least at the same level as “dot”. Since we are only interested in the shortest path, there’s no point adding “lot” from “dot”.
Now the more important stuff. How do we generate the list of words that differ from the current word by only 1 character? There are two options:
- Iterate over the list of words and compare the characters. The time complexity of this method is where n is the size of the dictionary and m is the length of each word.
- Iterate over all the alphabets, changing each character at a time and check if the new word is in the dictionary. The complexity, in this case, is as we are cycling through 26 alphabets for each character of the word.
For large dictionaries with small length words, the second approach is much more scalable than the first one.
But of course, the second approach requires us to be able to look up the presence of the generated word in the dictionary in constant time. This is the final important point I want to highlight. Such a requirement dictates that the words of the dictionary be stored in a hash set to allow constant time lookups.
A final word on the graph traversal. A lot of people are tempted to go with depth first search for any graph traversal. DFS is a very poor choice for solving shortest-path problems:
- Even if DFS finds the end node at a depth h, we cannot be sure that this is the shortest path unless we traverse rest of the graph.
- A DFS search might get stuck in a very deep exploration even though the shortest path was right at the surface.
You must always remember the properties of DFS and BFS and make the right choice to solve the problem at hand. These two traversals form the core of a lot of graph algorithms. Having clarity in the differences between these two traversals will help you go a long way.
Now that we have everything in place, let’s implement the solution. You can find the complete solution at 127_word_ladder.py
Here is the BFS implementation:
And here is the implementation for the get_next_words method whose task is to return the list of words from the dictionary that can be formed by changing just 1 character of the given word. The response is cached to avoid repeated computations:
This method is further optimized to only return words in the lower levels.