-
Notifications
You must be signed in to change notification settings - Fork 43
/
word-ladder.py
331 lines (289 loc) · 12 KB
/
word-ladder.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
"""
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 si for 1 <= i <= k is in wordList. Note that beginWord does not need to be in wordList.
sk == endWord
Given two words, beginWord and endWord, and a dictionary wordList, return the number of words in the shortest transformation sequence from beginWord to endWord, or 0 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, and wordList[i] consist of lowercase English letters.
beginWord != endWord
All the words in wordList are unique.
"""
# V0
# IDEA : BFS
# NOTE !!!
# 1) since we use BFS, so the solution will be shortest one
# 2) from problem : All the words in wordList are unique.
# -> so there is ONLY one possibility for next word within each iteration
class Solution(object):
def ladderLength(self, beginWord, endWord, wordList):
# we get non-duplicated list via set here
wordset = set(wordList)
bfs = collections.deque()
### NOTE : we use (beginWord, 1) data structure for not only get current word, but also sequence length
bfs.append((beginWord, 1))
while bfs:
word, length = bfs.popleft()
if word == endWord:
return length
"""
### NOTE : we looping elements in bfs here
### NOTE : we have 2 looping here : for i in range(len(word)), for c in "abcdefghijklmnopqrstuvwxyz"
"""
for i in range(len(word)):
for c in "abcdefghijklmnopqrstuvwxyz":
newWord = word[:i] + c + word[i + 1:]
# if newword is not qeual to original word as well (newWord != word )
if newWord in wordset and newWord != word:
# note : we need to remove the used word from wordset
wordset.remove(newWord)
bfs.append((newWord, length + 1))
return 0
# V1
# IDEA : BFS
# https://blog.csdn.net/fuxuemingzhu/article/details/82903681
class Solution(object):
def ladderLength(self, beginWord, endWord, wordList):
"""
:type beginWord: str
:type endWord: str
:type wordList: List[str]
:rtype: int
"""
wordset = set(wordList)
bfs = collections.deque()
bfs.append((beginWord, 1))
while bfs:
word, length = bfs.popleft()
if word == endWord:
return length
for i in range(len(word)):
for c in "abcdefghijklmnopqrstuvwxyz":
newWord = word[:i] + c + word[i + 1:]
if newWord in wordset and newWord != word:
wordset.remove(newWord)
bfs.append((newWord, length + 1))
return 0
### Test case : dev
# V1'
# IDEA : BFS
# https://leetcode.com/problems/word-ladder/discuss/40729/Compact-Python-solution
class Solution(object):
def ladderLength(self, beginWord, endWord, wordList):
wordList = set(wordList)
queue = collections.deque([[beginWord, 1]])
while queue:
word, length = queue.popleft()
if word == endWord:
return length
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
next_word = word[:i] + c + word[i+1:]
if next_word in wordList:
wordList.remove(next_word)
queue.append([next_word, length + 1])
return 0
# V1''
# IDEA : BFS
# https://leetcode.com/problems/word-ladder/discuss/157376/Python-(BFS)-tm
class Solution(object):
def ladderLength(self, start, end, arr):
arr = set(arr) #avoid TLE
q = collections.deque([(start, 1)])
visted = set()
alpha = string.ascii_lowercase #'abcd...z'
while q:
word, length = q.popleft()
if word == end:
return length
for i in range(len(word)):
for ch in alpha:
new_word = word[:i] + ch + word[i+1:]
if new_word in arr and new_word not in visted:
q.append((new_word, length + 1))
visted.add(new_word)
return 0
# V1
# IDEA : BFS
# https://blog.csdn.net/fuxuemingzhu/article/details/82903681
class Solution(object):
def ladderLength(self, beginWord, endWord, wordList):
"""
:type beginWord: str
:type endWord: str
:type wordList: List[str]
:rtype: int
"""
wordset = set(wordList)
if endWord not in wordset:
return 0
visited = set([beginWord])
chrs = [chr(ord('a') + i) for i in range(26)]
bfs = collections.deque([beginWord])
res = 1
while bfs:
len_bfs = len(bfs)
for _ in range(len_bfs):
origin = bfs.popleft()
for i in range(len(origin)):
originlist = list(origin)
for c in chrs:
originlist[i] = c
transword = "".join(originlist)
if transword not in visited:
if transword == endWord:
return res + 1
elif transword in wordset:
bfs.append(transword)
visited.add(transword)
res += 1
return 0
# V1'
# https://leetcode.com/problems/word-ladder/solution/
# IDEA : BFS
# time complexity : O(M**2 * N)
# space complexity : O(M**2 * N)
from collections import defaultdict
class Solution(object):
def ladderLength(self, beginWord, endWord, wordList):
"""
:type beginWord: str
:type endWord: str
:type wordList: List[str]
:rtype: int
"""
if endWord not in wordList or not endWord or not beginWord or not wordList:
return 0
# Since all words are of same length.
L = len(beginWord)
# Dictionary to hold combination of words that can be formed,
# from any given word. By changing one letter at a time.
all_combo_dict = defaultdict(list)
for word in wordList:
for i in range(L):
# Key is the generic word
# Value is a list of words which have the same intermediate generic word.
all_combo_dict[word[:i] + "*" + word[i+1:]].append(word)
# Queue for BFS
queue = collections.deque([(beginWord, 1)])
# Visited to make sure we don't repeat processing same word.
visited = {beginWord: True}
while queue:
current_word, level = queue.popleft()
for i in range(L):
# Intermediate words for current word
intermediate_word = current_word[:i] + "*" + current_word[i+1:]
# Next states are all the words which share the same intermediate state.
for word in all_combo_dict[intermediate_word]:
# If at any point if we find what we are looking for
# i.e. the end word - we can return with the answer.
if word == endWord:
return level + 1
# Otherwise, add it to the BFS Queue. Also mark it visited
if word not in visited:
visited[word] = True
queue.append((word, level + 1))
all_combo_dict[intermediate_word] = []
return 0
# V1''
# https://leetcode.com/problems/word-ladder/solution/
# IDEA : Bidirectional Breadth First Search
# time complexity : O(M**2 * N)
# space complexity : O(M**2 * N)
from collections import defaultdict
class Solution(object):
def __init__(self):
self.length = 0
# Dictionary to hold combination of words that can be formed,
# from any given word. By changing one letter at a time.
self.all_combo_dict = defaultdict(list)
def visitWordNode(self, queue, visited, others_visited):
current_word, level = queue.popleft()
for i in range(self.length):
# Intermediate words for current word
intermediate_word = current_word[:i] + "*" + current_word[i+1:]
# Next states are all the words which share the same intermediate state.
for word in self.all_combo_dict[intermediate_word]:
# If the intermediate state/word has already been visited from the
# other parallel traversal this means we have found the answer.
if word in others_visited:
return level + others_visited[word]
if word not in visited:
# Save the level as the value of the dictionary, to save number of hops.
visited[word] = level + 1
queue.append((word, level + 1))
return None
def ladderLength(self, beginWord, endWord, wordList):
"""
:type beginWord: str
:type endWord: str
:type wordList: List[str]
:rtype: int
"""
if endWord not in wordList or not endWord or not beginWord or not wordList:
return 0
# Since all words are of same length.
self.length = len(beginWord)
for word in wordList:
for i in range(self.length):
# Key is the generic word
# Value is a list of words which have the same intermediate generic word.
self.all_combo_dict[word[:i] + "*" + word[i+1:]].append(word)
# Queues for birdirectional BFS
queue_begin = collections.deque([(beginWord, 1)]) # BFS starting from beginWord
queue_end = collections.deque([(endWord, 1)]) # BFS starting from endWord
# Visited to make sure we don't repeat processing same word
visited_begin = {beginWord: 1}
visited_end = {endWord: 1}
ans = None
# We do a birdirectional search starting one pointer from begin
# word and one pointer from end word. Hopping one by one.
while queue_begin and queue_end:
# One hop from begin word
ans = self.visitWordNode(queue_begin, visited_begin, visited_end)
if ans:
return ans
# One hop from end word
ans = self.visitWordNode(queue_end, visited_end, visited_begin)
if ans:
return ans
return 0
# V2
# Time: O(n * d), n is length of string, d is size of dictionary
# Space: O(d)
from string import ascii_lowercase
class Solution(object):
def ladderLength(self, beginWord, endWord, wordList):
"""
:type beginWord: str
:type endWord: str
:type wordList: List[str]
:rtype: int
"""
distance, cur, visited, lookup = 0, [beginWord], set([beginWord]), set(wordList)
while cur:
next_queue = []
for word in cur:
if word == endWord:
return distance + 1
for i in range(len(word)):
for j in ascii_lowercase:
candidate = word[:i] + j + word[i+1:]
if candidate not in visited and candidate in lookup:
next_queue.append(candidate)
visited.add(candidate)
distance += 1
cur = next_queue
return 0