Word Ladder
Word Ladder:
A transformation sequence from word beginWord
to word endWord
using a dictionary wordList
is a sequence of words beginWord -> s1 -> s2 -> ... -> sk
such that:
- Every adjacent pair of words differs by a single letter.
- Every
s_i
for1 <= i <= k
is inwordList
. Note thatbeginWord
does not need to be inwordList
. s_k == endWord
Given two words,beginWord
andendWord
, and a dictionarywordList
, return the number of words in the shortest transformation sequence frombeginWord
toendWord
, or0
if no such sequence exists.
Example 1:
Input: beginWord = "hit", endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
Output: 5
Explanation: One shortest transformation sequence is
"hit" -> "hot" -> "dot" -> "dog" -> cog", which is 5 words long.
Example 2:
Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
Output: 0
Explanation: The endWord "cog" is not in wordList,
therefore there is no valid transformation sequence.
Constraints:
1 <= beginWord.length <= 10
endWord.length == beginWord.length
1 <= wordList.length <= 5000
wordList[i].length == beginWord.length
beginWord
,endWord
, andwordList[i]
consist of lowercase English letters.beginWord != endWord
- All the words in
wordList
are unique.
Try this Problem on your own or check similar problems:
Solution:
- Java
- JavaScript
- Python
- C++
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
Set<String> s = new HashSet<>(wordList);
if(!s.contains(endWord)) return 0;
Queue<String> q = new LinkedList<>();
Set<String> visited = new HashSet<>();
q.add(beginWord);
visited.add(beginWord);
int level = 1;
while(!q.isEmpty()){
int size = q.size();
while(size-->0){
String word = q.poll();
if(word.equals(endWord)) return level;
for(int j = 0; j < word.length(); ++j){
for(char l = 'a'; l <= 'z'; ++l){
char wordArr[] = word.toCharArray();
wordArr[j] = l;
String modifiedWord = new String(wordArr);
if(s.contains(modifiedWord) && !visited.contains(modifiedWord)){
q.add(modifiedWord);
visited.add(modifiedWord);
}
}
}
}
++level;
}
return 0;
}
/**
* @param {string} beginWord
* @param {string} endWord
* @param {string[]} wordList
* @return {number}
*/
var ladderLength = function (beginWord, endWord, wordList) {
const wordSet = new Set(wordList);
if (!wordSet.has(endWord)) return 0;
const queue = [];
const visited = new Set();
queue.push(beginWord);
visited.add(beginWord);
let level = 1;
while (queue.length > 0) {
const size = queue.length;
for (let i = 0; i < size; ++i) {
const word = queue.shift();
if (word === endWord) return level;
for (let j = 0; j < word.length; ++j) {
for (let l = 97; l <= 122; ++l) {
const wordArr = word.split("");
wordArr[j] = String.fromCharCode(l);
const modifiedWord = wordArr.join("");
if (wordSet.has(modifiedWord) && !visited.has(modifiedWord)) {
queue.push(modifiedWord);
visited.add(modifiedWord);
}
}
}
}
++level;
}
return 0;
};
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
wordSet = set(wordList)
if endWord not in wordSet:
return 0
queue = deque()
visited = set()
queue.append(beginWord)
visited.add(beginWord)
level = 1
while queue:
size = len(queue)
while size > 0:
word = queue.popleft()
if word == endWord:
return level
for j in range(len(word)):
for l in 'abcdefghijklmnopqrstuvwxyz':
wordArr = list(word)
wordArr[j] = l
modifiedWord = ''.join(wordArr)
if modifiedWord in wordSet and modifiedWord not in visited:
queue.append(modifiedWord)
visited.add(modifiedWord)
size -= 1
level += 1
return 0
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
unordered_set<string> wordSet(wordList.begin(), wordList.end());
if (wordSet.find(endWord) == wordSet.end())
return 0;
queue<string> q;
unordered_set<string> visited;
q.push(beginWord);
visited.insert(beginWord);
int level = 1;
while (!q.empty()) {
int size = q.size();
while (size-- > 0) {
string word = q.front();
q.pop();
if (word == endWord)
return level;
for (int j = 0; j < word.length(); ++j) {
for (char l = 'a'; l <= 'z'; ++l) {
string modifiedWord = word;
modifiedWord[j] = l;
if (wordSet.find(modifiedWord) != wordSet.end() && visited.find(modifiedWord) == visited.end()) {
q.push(modifiedWord);
visited.insert(modifiedWord);
}
}
}
}
++level;
}
return 0;
}
};
Time/Space Complexity:
- Time Complexity: O(m^2 * n)
- Space Complexity: O(mn)
Explanation:
To find the shortest path we could build a graph where each word is a vertex and there exists an edge between words only if they differ by maximum one letter. If we insert the beginWord
in the graph if it doesn't already exist, to find the shortest path in the graph we can use BFS since it finds the shortest path between a source and destination vertex in unweighted graph (each edge is of the same weight here). The algorithm is straightforward:
- Java
- JavaScript
- Python
- C++
class Solution {
private class Graph{
Set<Integer>[] graph;
int numberOfNodes;
public Graph(int numberOfNodes){
this.numberOfNodes = numberOfNodes;
graph = new Set[numberOfNodes];
for(int i = 0; i < numberOfNodes; ++i) graph[i] = new HashSet<>();
}
public void buildGraph(List<String> wordList){
for(int i = 0; i < wordList.size(); ++i){
for(int j = 0; j < wordList.size(); ++j){
if(i != j && diffByOne(wordList.get(i), wordList.get(j))){
graph[i].add(j);
graph[j].add(i);
}
}
}
}
private boolean diffByOne(String word1, String word2){
int diff = 0;
for(int i = 0; i < word1.length(); ++i){
if(word1.charAt(i) != word2.charAt(i)) ++diff;
if(diff > 1) return false;
}
return true;
}
private int bfs(int source, int destination){
Queue<Integer> q = new LinkedList<>();
boolean[] visited = new boolean[numberOfNodes];
q.add(source);
int level = 1;
while(!q.isEmpty()){
int size = q.size();
while(size-->0){
int v = q.poll();
visited[v] = true;
Set<Integer> neighbors = graph[v];
for(int neighbor : neighbors){
if(!visited[neighbor]){
q.add(neighbor);
if(neighbor == destination) return level;
}
}
}
++level;
}
return numberOfNodes + 1;
}
}
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
Map<String, Integer> wordToIdx = new HashMap<>();
for(int i = 0; i < wordList.size(); ++i){
wordToIdx.put(wordList.get(i), i);
}
if(!wordToIdx.containsKey(endWord)) return 0;
if(!wordToIdx.containsKey(beginWord)){
wordList.add(beginWord);
wordToIdx.put(beginWord, wordList.size() - 1);
}
Graph g = new Graph(wordList.size());
g.buildGraph(wordList);
int minDistance = g.bfs(wordToIdx.get(beginWord), wordToIdx.get(endWord)) + 1;
return minDistance > wordList.size() ? 0 : minDistance;
}
}
/**
* @param {string} beginWord
* @param {string} endWord
* @param {string[]} wordList
* @return {number}
*/
class Graph {
constructor(numberOfNodes) {
this.numberOfNodes = numberOfNodes;
this.graph = new Array(numberOfNodes);
for (let i = 0; i < numberOfNodes; i++) {
this.graph[i] = new Set();
}
}
buildGraph(wordList) {
for (let i = 0; i < wordList.length; i++) {
for (let j = 0; j < wordList.length; j++) {
if (i !== j && this.diffByOne(wordList[i], wordList[j])) {
this.graph[i].add(j);
this.graph[j].add(i);
}
}
}
}
diffByOne(word1, word2) {
let diff = 0;
for (let i = 0; i < word1.length; i++) {
if (word1.charAt(i) !== word2.charAt(i)) diff++;
if (diff > 1) return false;
}
return true;
}
bfs(source, destination) {
const queue = [];
const visited = new Array(this.numberOfNodes).fill(false);
queue.push(source);
let level = 1;
while (queue.length > 0) {
let size = queue.length;
while (size-- > 0) {
let v = queue.shift();
visited[v] = true;
const neighbors = this.graph[v];
for (let neighbor of neighbors) {
if (!visited[neighbor]) {
queue.push(neighbor);
if (neighbor === destination) return level;
}
}
}
level++;
}
return this.numberOfNodes + 1;
}
}
var ladderLength = function (beginWord, endWord, wordList) {
const wordToIdx = new Map();
for (let i = 0; i < wordList.length; i++) {
wordToIdx.set(wordList[i], i);
}
if (!wordToIdx.has(endWord)) return 0;
if (!wordToIdx.has(beginWord)) {
wordList.push(beginWord);
wordToIdx.set(beginWord, wordList.length - 1);
}
const g = new Graph(wordList.length);
g.buildGraph(wordList);
const minDistance =
g.bfs(wordToIdx.get(beginWord), wordToIdx.get(endWord)) + 1;
return minDistance > wordList.length ? 0 : minDistance;
};
class Graph:
def __init__(self, numberOfNodes):
self.numberOfNodes = numberOfNodes
self.graph = [set() for _ in range(numberOfNodes)]
def buildGraph(self, wordList):
for i in range(len(wordList)):
for j in range(len(wordList)):
if i != j and self.diffByOne(wordList[i], wordList[j]):
self.graph[i].add(j)
self.graph[j].add(i)
def diffByOne(self, word1, word2):
diff = 0
for i in range(len(word1)):
if word1[i] != word2[i]:
diff += 1
if diff > 1:
return False
return True
def bfs(self, source, destination):
q = deque()
visited = [False] * self.numberOfNodes
q.append(source)
level = 1
while q:
size = len(q)
while size > 0:
v = q.popleft()
visited[v] = True
neighbors = self.graph[v]
for neighbor in neighbors:
if not visited[neighbor]:
q.append(neighbor)
if neighbor == destination:
return level
size -= 1
level += 1
return self.numberOfNodes + 1
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
wordToIdx = {}
for i in range(len(wordList)):
wordToIdx[wordList[i]] = i
if endWord not in wordToIdx:
return 0
if beginWord not in wordToIdx:
wordList.append(beginWord)
wordToIdx[beginWord] = len(wordList) - 1
g = Graph(len(wordList))
g.buildGraph(wordList)
minDistance = g.bfs(wordToIdx[beginWord], wordToIdx[endWord]) + 1
return minDistance if minDistance <= len(wordList) else 0
class Graph {
public:
Graph(int numberOfNodes) {
this->numberOfNodes = numberOfNodes;
graph.resize(numberOfNodes);
}
void buildGraph(const vector<string>& wordList) {
for (int i = 0; i < wordList.size(); i++) {
for (int j = 0; j < wordList.size(); j++) {
if (i != j && diffByOne(wordList[i], wordList[j])) {
graph[i].insert(j);
graph[j].insert(i);
}
}
}
}
bool diffByOne(const string& word1, const string& word2) {
int diff = 0;
for (int i = 0; i < word1.size(); i++) {
if (word1[i] != word2[i]) {
diff++;
}
if (diff > 1) {
return false;
}
}
return true;
}
int bfs(int source, int destination) {
queue<int> q;
vector<bool> visited(numberOfNodes, false);
q.push(source);
int level = 1;
while (!q.empty()) {
int size = q.size();
while (size-- > 0) {
int v = q.front();
q.pop();
visited[v] = true;
const unordered_set<int>& neighbors = graph[v];
for (int neighbor : neighbors) {
if (!visited[neighbor]) {
q.push(neighbor);
if (neighbor == destination) {
return level;
}
}
}
}
level++;
}
return numberOfNodes + 1;
}
private:
int numberOfNodes;
vector<unordered_set<int>> graph;
};
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
unordered_map<string, int> wordToIdx;
for (int i = 0; i < wordList.size(); i++) {
wordToIdx[wordList[i]] = i;
}
if (wordToIdx.find(endWord) == wordToIdx.end()) {
return 0;
}
if (wordToIdx.find(beginWord) == wordToIdx.end()) {
wordList.push_back(beginWord);
wordToIdx[beginWord] = wordList.size() - 1;
}
Graph g(wordList.size());
g.buildGraph(wordList);
int minDistance = g.bfs(wordToIdx[beginWord], wordToIdx[endWord]) + 1;
return minDistance <= wordList.size() ? minDistance : 0;
}
};
But notices that we spend O(l * n^2)
(where l
is the wordLength
) for building the graph, we can cut this by implicitly building the graph starting from the beginWord
and checking if any letter modification leads to another word in the wordList
. The same BFS logic applies here, we just don't build the graph. The space complexity is O(mn)
since we can store at maximum n
the length of the wordList
words with length of m
(m == l == wordLength
). The time complexity is O(m^2 * n)
since we traverse over n
words and for each word we iterate over m
characters (length of words in wordList
) each time trying to create a modifiedWord
which needs to be hashed (checking if it exists in set, converting to array of character taking O(m)
time) leading to O(m^2)
per traversal.
- Java
- JavaScript
- Python
- C++
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
Set<String> s = new HashSet<>(wordList);
if(!s.contains(endWord)) return 0;
Queue<String> q = new LinkedList<>();
Set<String> visited = new HashSet<>();
q.add(beginWord);
visited.add(beginWord);
int level = 1;
while(!q.isEmpty()){
int size = q.size();
while(size-->0){
String word = q.poll();
if(word.equals(endWord)) return level;
for(int j = 0; j < word.length(); ++j){
for(char l = 'a'; l <= 'z'; ++l){
char wordArr[] = word.toCharArray();
wordArr[j] = l;
String modifiedWord = new String(wordArr);
if(s.contains(modifiedWord) && !visited.contains(modifiedWord)){
q.add(modifiedWord);
visited.add(modifiedWord);
}
}
}
}
++level;
}
return 0;
}
/**
* @param {string} beginWord
* @param {string} endWord
* @param {string[]} wordList
* @return {number}
*/
var ladderLength = function (beginWord, endWord, wordList) {
const wordSet = new Set(wordList);
if (!wordSet.has(endWord)) return 0;
const queue = [];
const visited = new Set();
queue.push(beginWord);
visited.add(beginWord);
let level = 1;
while (queue.length > 0) {
const size = queue.length;
for (let i = 0; i < size; ++i) {
const word = queue.shift();
if (word === endWord) return level;
for (let j = 0; j < word.length; ++j) {
for (let l = 97; l <= 122; ++l) {
const wordArr = word.split("");
wordArr[j] = String.fromCharCode(l);
const modifiedWord = wordArr.join("");
if (wordSet.has(modifiedWord) && !visited.has(modifiedWord)) {
queue.push(modifiedWord);
visited.add(modifiedWord);
}
}
}
}
++level;
}
return 0;
};
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
wordSet = set(wordList)
if endWord not in wordSet:
return 0
queue = deque()
visited = set()
queue.append(beginWord)
visited.add(beginWord)
level = 1
while queue:
size = len(queue)
while size > 0:
word = queue.popleft()
if word == endWord:
return level
for j in range(len(word)):
for l in 'abcdefghijklmnopqrstuvwxyz':
wordArr = list(word)
wordArr[j] = l
modifiedWord = ''.join(wordArr)
if modifiedWord in wordSet and modifiedWord not in visited:
queue.append(modifiedWord)
visited.add(modifiedWord)
size -= 1
level += 1
return 0
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
unordered_set<string> wordSet(wordList.begin(), wordList.end());
if (wordSet.find(endWord) == wordSet.end())
return 0;
queue<string> q;
unordered_set<string> visited;
q.push(beginWord);
visited.insert(beginWord);
int level = 1;
while (!q.empty()) {
int size = q.size();
while (size-- > 0) {
string word = q.front();
q.pop();
if (word == endWord)
return level;
for (int j = 0; j < word.length(); ++j) {
for (char l = 'a'; l <= 'z'; ++l) {
string modifiedWord = word;
modifiedWord[j] = l;
if (wordSet.find(modifiedWord) != wordSet.end() && visited.find(modifiedWord) == visited.end()) {
q.push(modifiedWord);
visited.insert(modifiedWord);
}
}
}
}
++level;
}
return 0;
}
};