126. Word Ladder II

Given two words (beginWord and endWord), and a dictionary's word list, find all shortest transformation sequence(s) from beginWord to endWord, such that:

  1. Only one letter can be changed at a time

  2. Each transformed word must exist in the word list. Note that beginWord is not a transformed word.

Note:

  • Return an empty list 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 endWord are non-empty and are not the same.

Example 1:

Input:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]

Output:
[
  ["hit","hot","dot","dog","cog"],
  ["hit","hot","lot","log","cog"]
]

Example 2:

Input:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]

Output: []

Explanation: The endWord "cog" is not in wordList, therefore no possible transformation.
// Double-End BFS + Backtracking
void getPaths(const string& word, 
              const string& endWord, 
              const unordered_map<string, vector<string> >& children, 
              vector<string>& path, 
              vector<vector<string> >& res) 
{
    if (word == endWord) {
        res.push_back(path);
        return;
    }
    const auto it = children.find(word);
    if (it == children.cend()) return;
    for (const string& child : it->second) {
        path.push_back(child);
        getPaths(child, endWord, children, path, res);
        path.pop_back();
    }
}

vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
    vector<vector<string> > res;
    unordered_set<string> dict(wordList.begin(), wordList.end());
    if (!dict.count(endWord)) return res;
    unordered_set<string> q1({beginWord}), q2({endWord});
    unordered_map<string, vector<string> > children;
    bool found = false, backward = false;
    int l = beginWord.length();
    while (!q1.empty() && !q2.empty() && !found) {
        // Do BFS from the smaller queue
        if (q1.size() > q2.size()) {
            swap(q1, q2);
            backward = !backward;
        }
        for (const string& w1 : q1) dict.erase(w1);
        for (const string& w2 : q2) dict.erase(w2);
        unordered_set<string> tmp_q;
        for (const string& word : q1) {
            string cur = word;
            for (int i = 0; i < l; ++i) {
                char ch = cur[i];
                for (char j = 'a'; j <= 'z'; ++j) {
                    cur[i] = j;
                    const string* parent = &word;
                    const string* child = &cur;
                    if (backward) swap(parent, child);
                    if (q2.count(cur)) {
                        found = true;
                        children[*parent].push_back(*child);
                    } else if (dict.count(cur) && !found) {
                        tmp_q.insert(cur);
                        children[*parent].push_back(*child);
                    }
                }
                cur[i] = ch;
            }
        }
        swap(q1, tmp_q);
    }
    if (found) {
        vector<string> path({beginWord});
        getPaths(beginWord, endWord, children, path, res);
    }
    return res;
}
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) { // time: O(n * str_len); space: O(n * str_len)
    unordered_set<string> words(wordList.begin(), wordList.end());
    vector<vector<string> > res;
    queue<vector<string> > paths({{beginWord}});
    unordered_set<string> visited; // the words visited in the current level
    int level = 1, minLevel = INT_MAX;
    while (!paths.empty()) {
        // a level
        for (int k = paths.size() - 1; k >= 0; --k) {
            vector<string> path = paths.front(); paths.pop();
            string lastWord = path.back();
            for (int i = 0; i < lastWord.length(); ++i) {
                string newWord = lastWord;
                for (char ch = 'a'; ch <= 'z'; ++ch) {
                    newWord[i] = ch;
                    if (words.count(newWord)) {
                        vector<string> newPath = path;
                        newPath.push_back(newWord);
                        visited.insert(newWord);
                        if (newWord == endWord) {
                            minLevel = level;
                            res.push_back(newPath);
                        } else {
                            paths.push(newPath);
                        }
                    }
                }
            }
        }
        if (++level > minLevel) break; // pruning, already found minLevel path combination
        else {
            for (auto& str : visited) words.erase(str);
            visited.clear();   
        }
    }
    return res;
}

Last updated

Was this helpful?