LC 126. Word Ladder II
https://leetcode.com/problems/word-ladder-ii/
我觉得这题很有意思,是里面涉及到了两个点: 如何判断两个字符串之间只相差一个字符,以及如何枚举所有的最短路径。
先说第一个问题,如何判断两个字符串之间只相差一个字符。比如 "hot"和"hit"就差一个字符,而"hot"和"hig"则相差两个字符。 假设所有字符串长度是K,共有N个字符串。
一种办法是枚举每个位置的所有字符,创建字典然后去查询。以上面例子说明:
- 在位置0, hot可以产生这些字符串 aot, bot, cot, …zot
- 在位置1上,hot可以产生 hat, hbt .. hzt
- 可以看到在枚举位置1的时候,就会和 "hit" 字符串重合
时间复杂度是O(26 * N * K^2), 空间复杂度是O(26 * N * K)
for i in range(k):
subwords = defaultdict(list)
for idx, w in enumerate(wordList):
for c in range(26):
x = chr(ord('a') + c)
if x == w[i]:
continue
s = w[:i] + x + w[i + 1:]
subwords[s].append(idx)
for idx, w in enumerate(wordList):
xs = subwords[w]
for x in xs:
adj[idx].append(x)
这里还有另外一个办法,同样我们还是枚举所有的位置
- 但那是在这些位置上不是枚举字符,而是直接删除
- 在位置0上, hot->ot, hit->it, hig->ig
- 在位置1上, hot->ht, hit->ht, hig->hg
- 可以看到在位置1上出现了重合
这样做时间复杂度是O(N * K^2). 空间复杂度是O(N * K). 不要小看这个常数项,提交之后时间可以从1400ms缩减到200ms.
for i in range(k):
wl = [(x[:i] + x[i + 1:], idx) for (idx, x) in enumerate(wordlist)]
from collections import defaultdict
groups = defaultdict(list)
for w, idx in wl:
groups[w].append(idx)
for w, xs in groups.items():
for j in range(len(xs)):
for k in range(j + 1, len(xs)):
x, y = xs[j], xs[k]
G[x].append(y)
G[y].append(x)
第二个问题就是如何枚举所有最短的路径。经典的最短路径比如BFS或者是Dijkstra算法,只给出其中一条最短路径。但是这些算法基础上稍微扩展一些,其实就可以得到所有最短路径。
以BFS最短路径为例(如果扩展到dijkstra应该也是适用的)
- 如果从u->v点,到达v点的长度是d
- 还存在u'->v点,到达v点的长度也是d
- 但是如果u2->v点,到达v点的长度>=d的话,那么可以舍弃
- 记录方式可以有两种,按照前向和候向记录
- 前向是 forward[u].append(v)
- 后向是 backward[v].append(u)
将这些前向/后向集合记录下来之后,就可以使用dfs的办法来获取所有的最短路径了。
在代码实现上,我们还是使用BFS算法。
- depth[v]表示到达v这个节点的最短路径长度是多少
- fwd_links 和 back_links 分别表示前向后向(其实只需要保存一个就行)
from collections import deque
dq = deque()
depth = [0] * n
depth[begin] = 1
dq.append(begin)
fwd_links = [[] for _ in range(n)]
back_links = [[] for _ in range(n)]
while dq:
s = dq.popleft()
if s == end:
break
for t in adj[s]:
if depth[t] == 0:
depth[t] = depth[s] + 1
dq.append(t)
if depth[t] == (depth[s] + 1):
fwd_links[s].append(t)
back_links[t].append(s)
至于dfs得到所有最短路径,可以使用迭代也可以使用递归。我这里给出前向迭代和后向递归的代码。
# ==============================
if fwd_iter:
ans = [[begin]]
for i in range(1, depth[end]):
tmp = []
for path in ans:
e = path[-1]
d = len(path) + 1
for v in fwd_links[e]:
assert depth[v] == d
tmp.append(path + [v])
ans = tmp
ans = [[wordList[i] for i in path] for path in ans if path[-1] == end]
return ans
# ====================
if back_rec:
ans = []
def dfs(v, path, d):
if depth[v] > d:
return
if v == begin:
ans.append([wordList[i] for i in reversed(path)])
return
for t in back_links[v]:
path.append(t)
dfs(t, path, d - 1)
path.pop()
dfs(end, [end], depth[end])
return ans