127 Word Ladder

Given two words (beginWordand endWord), and a dictionary's word list, find the length of shortest transformation sequence from beginWord to endWord, such that:
  1. 1.
    Only one letter can be changed at a time.
  2. 2.
    Each transformed word must exist in the word list. Note that beginWordis not a transformed word.
For example,
Given:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log","cog"]
As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog", return its length 5.
Note:
  • Return 0 if there is no such transformation sequence.
  • All words have the same length.
  • All words contain only lowercase alphabetic characters.
  • You may assume no duplicates in the word list.
  • You may assume _beginWord and _endWordare non-empty and are not the same.
The Idea: Build a graph. For this graph define a link between two words a and b if difference(a,b) == 1. Do this for every word. Then BFS through this graph, while using a visited map prohibits cyclic roots.
Complexity: O(n^2 * m + n) time where m is length of the word, and n is the number of words. O(n^2 * m) for prepossessing, and then O(n) time for BFS. Space complexity will be O(n^2 + n + n); respectively for the graph itself, the visited set, and queue.
// assumption that size(a) == size(b)
int difference(const string &a, const string &b) {
int diff = 0;
for (int i = 0; i < a.size(); i++) {
if (a[i] != b[i]) diff++;
}
return diff;
}
unordered_map<string, unordered_set<string>> get_graph_diff_1(const vector<string> &wordList) {
unordered_map<string, unordered_set<string>> map;
map.reserve(wordList.size());
for (const string &s : wordList) {
map.insert({ s, unordered_set<string>() });
for (const string &s2 : wordList) {
if (difference(s2, s) == 1) {
map.at(s).insert(s2);
}
}
}
return map;
}
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
wordList.push_back(beginWord);
unordered_map<string, unordered_set<string>> graph = get_graph_diff_1(wordList);
unordered_set<string> visited(wordList.begin(), wordList.end());
queue<pair<string, int>> q;
q.push({beginWord, 1});
while (!q.empty()) {
pair<string, int> front = q.front(); q.pop();
visited.erase(front.first);
for (auto &s : graph[front.first]) {
if (s == endWord)
return front.second + 1;
if (visited.find(s) != visited.end()) {
q.push({ s, front.second + 1 });
}
}
}
return 0;
}
Testing
int main() {
vector<string> word_bank = { "hot","dot","dog","lot","log","cog" };
cout << ladderLength("hit", "cog", word_bank) << endl;
vector<string> word_bank2 = { "hot", "dot", "dog", "lot", "log" };
cout << ladderLength("hit", "cog", word_bank2) << endl;
}
Python
import queue
class Solution:
def difference(self, a, b):
"""
:param a: str - word1
:param b: str - word2
:return: int - number of different characters between a and b
under the assumption that len(a) = len(b)
"""
diff = 0
for c1, c2 in zip(a,b):
if c1 != c2:
diff += 1
return diff
def get_graph_diff_1(self, word_list):
"""
:param word_list: List[str] - list of words
:return: 2d map - relationship of words that differ
by one connection
"""
return dict([word, set(word2 for word2 in word_list if self.difference(word, word2) == 1)] for word in word_list)
def ladderLength(self, beginWord, endWord, wordList):
"""
:param beginWord: str - word to start with
:param endWord: str - target word to end
:param wordList: List[str] - bank of valid words (same length)
:return: int - minimum distance to get between start and end words
"""
wordList.append(beginWord)
graph = self.get_graph_diff_1(wordList)
visited = set(word for word in wordList)
q = queue.Queue()
q.put((beginWord, 1))
while not q.empty():
front = q.get()
if visited.__contains__(front[0]):
visited.remove(front[0])
for word in graph[front[0]]:
if word == endWord:
return front[1] + 1
if visited.__contains__(word):
q.put((word, front[1] + 1))
return 0
obj = Solution()
word_list = ["hot", "dot", "dog", "lot", "log"]
print(obj.ladderLength("hit", "cog", word_list))