|
| 1 | +--- |
| 2 | +id: word-ladder-II |
| 3 | +title: Word ladder II solution |
| 4 | +sidebar_label: 0126 Word ladder II |
| 5 | +tags: |
| 6 | + - String |
| 7 | + - BFS (Breadth-First Search) |
| 8 | + - Backtracking |
| 9 | + - Graph |
| 10 | + - LeetCode |
| 11 | + - Python |
| 12 | + - Java |
| 13 | + - C++ |
| 14 | +description: "This is a solution to the word ladder II problem on LeetCode." |
| 15 | +--- |
| 16 | + |
| 17 | +## Problem Description |
| 18 | + |
| 19 | +A transformation sequence from word beginWord to word endWord using a dictionary wordList is a sequence of words beginWord $s1 -> s2 -> ... -> sk$ such that: |
| 20 | + |
| 21 | +Every adjacent pair of words differs by a single letter. |
| 22 | +Every si for $1 <= i <= k$ is in wordList. Note that beginWord does not need to be in wordList. |
| 23 | +sk == endWord |
| 24 | +Given two words, beginWord and endWord, and a dictionary wordList, return all the shortest transformation sequences from beginWord to endWord, or an empty list if no such sequence exists. Each sequence should be returned as a list of the words [beginWord, s1, s2, ..., sk]. |
| 25 | + |
| 26 | +### Examples |
| 27 | + |
| 28 | +**Example 1:** |
| 29 | + |
| 30 | +``` |
| 31 | +Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] |
| 32 | +Output: [["hit","hot","dot","dog","cog"],["hit","hot","lot","log","cog"]] |
| 33 | +Explanation: There are 2 shortest transformation sequences: |
| 34 | +"hit" -> "hot" -> "dot" -> "dog" -> "cog" |
| 35 | +"hit" -> "hot" -> "lot" -> "log" -> "cog" |
| 36 | +``` |
| 37 | + |
| 38 | +**Example 2:** |
| 39 | + |
| 40 | +``` |
| 41 | +Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"] |
| 42 | +Output: [] |
| 43 | +Explanation: The endWord "cog" is not in wordList, therefore there is no valid transformation sequence. |
| 44 | +``` |
| 45 | + |
| 46 | +### Constraints |
| 47 | + |
| 48 | +- $1 <= beginWord.length <= 5$ |
| 49 | +- $endWord.length == beginWord.length$ |
| 50 | +- $1 <= wordList.length <= 500$ |
| 51 | +- $wordList[i].length == beginWord.length$ |
| 52 | +- beginWord, endWord, and wordList[i] consist of lowercase English letters. |
| 53 | +- beginWord != endWord |
| 54 | +- All the words in wordList are unique. |
| 55 | +- The sum of all shortest transformation sequences does not exceed 105. |
| 56 | + |
| 57 | +## Solution for Word Ladder II Problem |
| 58 | + |
| 59 | +<Tabs> |
| 60 | + <TabItem value="Python 3" label="Python 3"> |
| 61 | + |
| 62 | +### Approach : |
| 63 | + |
| 64 | +#### Intuition |
| 65 | + |
| 66 | +Define a helper function neighbors(word) that generates all the possible words by changing a single character in the given word. |
| 67 | +Initialize a variable words as a dictionary with beginWord as the key and a lambda function returning [[beginWord]] as the value. This dictionary will keep track of all possible transformation sequences. |
| 68 | +Initialize a set unvisited containing all words in wordList except beginWord. |
| 69 | +Perform BFS: |
| 70 | +While there are words in words and endWord is not yet found in words: |
| 71 | +Increment a counter i. |
| 72 | +Initialize a new dictionary new_words to store the next layer of words. |
| 73 | +Iterate through each word s in words: |
| 74 | +Generate all possible neighbors ss of s. |
| 75 | +If ss is in unvisited, create a lambda function get_seqs that appends ss to each sequence of words ending with s, and add ss and get_seqs to new_words. |
| 76 | +Update words to new_words. |
| 77 | +Remove the keys of new_words from unvisited. |
| 78 | +Return the transformation sequences ending with endWord. |
| 79 | + |
| 80 | + |
| 81 | +#### Code in Different Languages |
| 82 | + |
| 83 | +<Tabs> |
| 84 | + <TabItem value="Python3" label="Python3"> |
| 85 | + <SolutionAuthor name="@mahek0620"/> |
| 86 | + ```python3 |
| 87 | + class Solution: |
| 88 | + def findLadders( |
| 89 | + self, beginWord: str, endWord: str, wordList: List[str] |
| 90 | + ) -> List[List[str]]: |
| 91 | + def neighbors(word): |
| 92 | + for i in range(len(word)): |
| 93 | + for char in "abcdefghijklmnopqrstuvwxyz": |
| 94 | + yield word[:i] + char + word[i + 1 :] |
| 95 | + |
| 96 | + i = 1 |
| 97 | + words = {beginWord: lambda: [[beginWord]]} |
| 98 | + unvisited = set(wordList) |
| 99 | + while words and endWord not in words: |
| 100 | + i += 1 |
| 101 | + new_words = defaultdict(lambda: lambda: []) |
| 102 | + for s in words: |
| 103 | + for ss in neighbors(s): |
| 104 | + if ss in unvisited: |
| 105 | + |
| 106 | + def get_seqs(capture=(ss, new_words[ss], words[s])): |
| 107 | + ss, ss_get_seqs, s_get_seqs = capture |
| 108 | + seqs = ss_get_seqs() |
| 109 | + for seq in s_get_seqs(): |
| 110 | + seq.append(ss) |
| 111 | + seqs.append(seq) |
| 112 | + return seqs |
| 113 | + |
| 114 | + new_words[ss] = get_seqs |
| 115 | + words = new_words |
| 116 | + unvisited -= words.keys() |
| 117 | + return words[endWord]() |
| 118 | + |
| 119 | + ``` |
| 120 | + |
| 121 | + </TabItem> |
| 122 | + <TabItem value="Java" label="Java"> |
| 123 | + <SolutionAuthor name="@mahek0620"/> |
| 124 | + ```java |
| 125 | + class Solution { |
| 126 | + public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) { |
| 127 | + List<List<String>> ans = new ArrayList<>(); |
| 128 | + Map<String, Set<String>> reverse = new HashMap<>(); // reverse graph start from endWord |
| 129 | + Set<String> wordSet = new HashSet<>(wordList); // remove the duplicate words |
| 130 | + wordSet.remove(beginWord); // remove the first word to avoid cycle path |
| 131 | + Queue<String> queue = new LinkedList<>(); // store current layer nodes |
| 132 | + queue.add(beginWord); // first layer has only beginWord |
| 133 | + Set<String> nextLevel = new HashSet<>(); // store nextLayer nodes |
| 134 | + boolean findEnd = false; // find endWord flag |
| 135 | + while (!queue.isEmpty()) { // traverse current layer nodes |
| 136 | + String word = queue.remove(); |
| 137 | + for (String next : wordSet) { |
| 138 | + if (isLadder(word, next)) { // is ladder words |
| 139 | + // construct the reverse graph from endWord |
| 140 | + Set<String> reverseLadders = reverse.computeIfAbsent(next, k -> new HashSet<>()); |
| 141 | + reverseLadders.add(word); |
| 142 | + if (endWord.equals(next)) { |
| 143 | + findEnd = true; |
| 144 | + } |
| 145 | + nextLevel.add(next); // store next layer nodes |
| 146 | + } |
| 147 | + } |
| 148 | + if (queue.isEmpty()) { // when current layer is all visited |
| 149 | + if (findEnd) break; // if find the endWord, then break the while loop |
| 150 | + queue.addAll(nextLevel); // add next layer nodes to queue |
| 151 | + wordSet.removeAll(nextLevel); // remove all next layer nodes in wordSet |
| 152 | + nextLevel.clear(); |
| 153 | + } |
| 154 | + } |
| 155 | + if (!findEnd) return ans; // if can't reach endWord from startWord, then return ans. |
| 156 | + Set<String> path = new LinkedHashSet<>(); |
| 157 | + path.add(endWord); |
| 158 | + // traverse reverse graph from endWord to beginWord |
| 159 | + findPath(endWord, beginWord, reverse, ans, path); |
| 160 | + return ans; |
| 161 | + } |
| 162 | + |
| 163 | + |
| 164 | + private void findPath(String endWord, String beginWord, Map<String, Set<String>> graph, |
| 165 | + List<List<String>> ans, Set<String> path) { |
| 166 | + Set<String> next = graph.get(endWord); |
| 167 | + if (next == null) return; |
| 168 | + for (String word : next) { |
| 169 | + path.add(word); |
| 170 | + if (beginWord.equals(word)) { |
| 171 | + List<String> shortestPath = new ArrayList<>(path); |
| 172 | + Collections.reverse(shortestPath); // reverse words in shortest path |
| 173 | + ans.add(shortestPath); // add the shortest path to ans. |
| 174 | + } else { |
| 175 | + findPath(word, beginWord, graph, ans, path); |
| 176 | + } |
| 177 | + path.remove(word); |
| 178 | + } |
| 179 | + } |
| 180 | + |
| 181 | + private boolean isLadder(String s, String t) { |
| 182 | + if (s.length() != t.length()) return false; |
| 183 | + int diffCount = 0; |
| 184 | + int n = s.length(); |
| 185 | + for (int i = 0; i < n; i++) { |
| 186 | + if (s.charAt(i) != t.charAt(i)) diffCount++; |
| 187 | + if (diffCount > 1) return false; |
| 188 | + } |
| 189 | + return diffCount == 1; |
| 190 | + }} |
| 191 | + ``` |
| 192 | + |
| 193 | + </TabItem> |
| 194 | + <TabItem value="C++" label="C++"> |
| 195 | + <SolutionAuthor name="@mahek0620"/> |
| 196 | + ```cpp |
| 197 | + class Solution { |
| 198 | +public: |
| 199 | + vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) { |
| 200 | + int n=wordList.size(); |
| 201 | + |
| 202 | + unordered_set<string> uset; |
| 203 | + for(auto const &it:wordList){ |
| 204 | + if(it!=beginWord) uset.insert(it); |
| 205 | + } |
| 206 | + |
| 207 | + queue<string> q; |
| 208 | + q.push(beginWord); |
| 209 | + |
| 210 | + vector<pair<string,int>> levels; |
| 211 | + int cnt=0; |
| 212 | + bool flag=false; |
| 213 | + |
| 214 | + while(!q.empty()){ |
| 215 | + int sz=q.size(); |
| 216 | + |
| 217 | + for(int i=0;i<sz;i++){ |
| 218 | + string node=q.front(); |
| 219 | + q.pop(); |
| 220 | + |
| 221 | + levels.push_back({node,cnt}); |
| 222 | + |
| 223 | + if(node==endWord){ |
| 224 | + flag=true; |
| 225 | + break; |
| 226 | + } |
| 227 | + |
| 228 | + for(int i=0;i<node.length();i++){ |
| 229 | + char original=node[i]; |
| 230 | + for(char j='a';j<='z';j++){ |
| 231 | + if(original==j) continue; |
| 232 | + |
| 233 | + node[i]=j; |
| 234 | + if(uset.find(node)!=uset.end()){ |
| 235 | + q.push(node); |
| 236 | + uset.erase(node); |
| 237 | + } |
| 238 | + } |
| 239 | + node[i]=original; |
| 240 | + } |
| 241 | + } |
| 242 | + if(flag) break; |
| 243 | + cnt++; |
| 244 | + } |
| 245 | + |
| 246 | + vector<vector<string>> ans; |
| 247 | + |
| 248 | + if(!flag){ |
| 249 | + return ans; |
| 250 | + } |
| 251 | + |
| 252 | + vector<pair<string,int>>::reverse_iterator it=levels.rbegin(); |
| 253 | + |
| 254 | + vector<string> vec; |
| 255 | + dfs(endWord,levels,ans,vec,it,cnt,beginWord); |
| 256 | + |
| 257 | + return ans; |
| 258 | + } |
| 259 | + |
| 260 | +private: |
| 261 | + |
| 262 | + void dfs(string str,vector<pair<string,int>> &levels,vector<vector<string>> &ans,vector<string> &vec,vector<pair<string,int>>::reverse_iterator it,int cnt,string &beginWord){ |
| 263 | + if(str==beginWord){ |
| 264 | + vec.push_back(str); |
| 265 | + reverse(vec.begin(),vec.end()); |
| 266 | + ans.push_back(vec); |
| 267 | + reverse(vec.begin(),vec.end()); |
| 268 | + vec.pop_back(); |
| 269 | + |
| 270 | + return; |
| 271 | + } |
| 272 | + |
| 273 | + while(it!=levels.rend() && it->second==cnt){ |
| 274 | + it++; |
| 275 | + } |
| 276 | + |
| 277 | + while(it!=levels.rend() && it->second==cnt-1){ |
| 278 | + if(isPossible(str,it->first)){ |
| 279 | + vec.push_back(str); |
| 280 | + dfs(it->first,levels,ans,vec,it,cnt-1,beginWord); |
| 281 | + vec.pop_back(); |
| 282 | + } |
| 283 | + it++; |
| 284 | + } |
| 285 | + } |
| 286 | + |
| 287 | + bool isPossible(string str1,string str2){ |
| 288 | + int n=str1.length(); |
| 289 | + |
| 290 | + int diff=0; |
| 291 | + for(int i=0;i<n;i++){ |
| 292 | + if(str1[i]!=str2[i]){ |
| 293 | + diff++; |
| 294 | + } |
| 295 | + |
| 296 | + if(diff>1) return false; |
| 297 | + } |
| 298 | + |
| 299 | + return true; |
| 300 | + } |
| 301 | +}; |
| 302 | + ``` |
| 303 | + |
| 304 | + </TabItem> |
| 305 | +</Tabs> |
| 306 | + |
| 307 | + |
| 308 | +</TabItem> |
| 309 | + |
| 310 | +</Tabs> |
| 311 | + |
| 312 | +## References |
| 313 | + |
| 314 | +- **LeetCode Problem**: [Word Ladder II](https://leetcode.com/problems/word-ladder-ii/) |
| 315 | + |
| 316 | +- **Solution Link**: [LeetCode Solution](https://leetcode.com/problems/word-ladder-ii/solution/) |
| 317 | + |
| 318 | +- **Authors GeeksforGeeks Profile:** [Mahek Patel](https://leetcode.com/u/mahekrpatel611/) |
0 commit comments