Given two words (beginWordand endWord), and a dictionary's word list, find the length of shortest transformation sequence from beginWord to endWord, such that:
Only one letter can be changed at a time.
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;
}
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))