题目:​​原题链接​(困难)

标签:字符串、广度优先遍历、回溯算法、图、图-无向图

解法

时间复杂度

空间复杂度

执行用时

Ans 1 (Python)

O(LN×C) : 其中L为最短路径长度,C为字符串长度

O(LN×C) : 其中L为最短路径长度,C为字符串长度

超出时间限制

Ans 2 (Python)

O(N2×C) : 其中C为字符串长度

O(N2×C) : 其中C为字符串长度

超出时间限制

Ans 3 (Python)

O(N×C×26)

O(N×C)

2128ms (11.54%)

Ans 3 (Python)

O(N×C)

O(N×C×26)

300ms (55.87%)


LeetCode的Python执行用时随缘,只要时间复杂度没有明显差异,执行用时一般都在同一个量级,仅作参考意义。


解法一(朴素的广度优先遍历):

class Solution:
def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]:
N = len(beginWord)

# 比较两个字符串是否可以通过一次转换获得
def check_transform(str1, str2):
differences = 0
for i in range(N):
if str1[i] != str2[i]:
differences += 1
if differences >= 2:
return False
return differences == 1

# 处理目标词不存在于词典的情况
if endWord not in wordList:
return []

visited = set()
now_paths = [(beginWord,)] # 当前路径
ans = []
while now_paths:
now_visited = set()
next_paths = []
for path in now_paths:
now = path[-1]
for word in wordList:
if word not in visited and check_transform(now, word):
if word == endWord:
ans.append(list(path + (word,)))
else:
now_visited.add(word)
next_paths.append(path + (word,))
now_paths = next_paths
visited.update(now_visited)
if ans:
break
return ans

解法二(无向图的广度优先搜索):


构造无向图的时间复杂度: O(N2×C)

遍历的时间复杂度: O(N2)


class Word:
__slots__ = "name", "near", "distance", "paths"

def __init__(self, name):
self.name = name
self.near = set() # 邻边列表
self.distance = float("inf") # 距离
self.paths = [] # 路径


class Solution:
def __init__(self):
self.N = 0 # 单词长度
self.words = {} # 图的结点

def check_transform(self, str1, str2):
"""比较两个单词是否可以通过一次转换获得"""
differences = 0
for i in range(self.N):
if str1[i] != str2[i]:
differences += 1
if differences >= 2:
return False
return differences == 1

def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]:
self.N = len(beginWord) # 单词长度

# 处理目标词不存在于词典的情况
if endWord not in wordList:
return []

# 将开始词添加到无向图中
wordList.append(beginWord)

# 构造无向图中的结点
for word in wordList:
self.words[word] = Word(word)

# 构造无向图中的边
for i in range(len(wordList)):
for j in range(i + 1, len(wordList)):
word1 = wordList[i]
word2 = wordList[j]
if self.check_transform(wordList[i], wordList[j]):
self.words[word1].near.add(self.words[word2])
self.words[word2].near.add(self.words[word1])

# 将开始词的距离和路径
self.words[beginWord].distance = 0
self.words[beginWord].paths = [[beginWord]]

now_words = [beginWord]
while now_words:
has_find_end = False
next_words = set()
for name in now_words:
node = self.words[name] # 结点
now_distance = node.distance
now_paths = node.paths
for near in node.near:
if near.name == endWord:
has_find_end = True
if near.distance >= now_distance + 1:
if near.distance > now_distance + 1:
near.paths = []
near.distance = now_distance + 1
for now_path in now_paths:
near.paths.append(now_path + [near.name])
next_words.add(near.name)

now_words = list(next_words)

if has_find_end:
break

return self.words[endWord].paths

解法三(集合用于记录当前节点):

class Solution:
def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]:
N = len(beginWord) # 单词长度
word_set = set(wordList) # 单词集合

# 处理目标词不存在于词典的情况
if endWord not in wordList:
return []

# 寻找所有相邻结点
def near(word):
near_words = []
arr = list(word)
for i in range(N): # 逐个字符遍历
ch = arr[i]
for code in range(97, 123): # 每个字符遍历替换所有字母
arr[i] = chr(code)
new_word = "".join(arr)
if new_word in word_set and new_word not in marked:
near_words.append(new_word)
arr[i] = ch
return near_words

marked = set() # 已访问的节点
queues = [[beginWord]] # 当前路径
ans = []

while queues:
new_queues = [] # 新的深度的路径
has_find_aim = False # 是否已找到目标词

# 将上一个深度的结点加入到已访问的节点
for queue in queues:
marked.add(queue[-1])

# 遍历寻找新的路径
for queue in queues:
for word in near(queue[-1]):
path = queue + [word]
if word == endWord:
ans.append(path)
has_find_aim = True
new_queues.append(path)

queues = new_queues

if has_find_aim: # 判断是否已经找到目标词
break

return ans

解法四(优化解法三计算邻边的方法):

class Solution:
def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]:
N = len(beginWord) # 单词长度
word_set = set(wordList) # 单词集合

# 处理目标词不存在于词典的情况
if endWord not in wordList:
return []

# 初始化单词列表:将单词列表中每个单词的每个字符都替换为*,用以在O(C)的时间复杂度内计算邻边
# 当前步骤时间复杂度:N×C
word_hash = collections.defaultdict(list)
for word in wordList:
for i in range(N):
word_hash[word[:i] + "*" + word[i + 1:]].append(word)

# 寻找所有相邻结点
def near(word):
near_words = []
for i in range(N): # 逐个字符遍历
for new_word in word_hash[word[:i] + "*" + word[i + 1:]]:
if new_word in word_set and new_word not in marked:
near_words.append(new_word)
return near_words

marked = set() # 已访问的节点
queues = [[beginWord]] # 当前路径
ans = []

while queues:
new_queues = [] # 新的深度的路径
has_find_aim = False # 是否已找到目标词

# 将上一个深度的结点加入到已访问的节点
for queue in queues:
marked.add(queue[-1])

# 遍历寻找新的路径
for queue in queues:
for word in near(queue[-1]):
path = queue + [word]
if word == endWord:
ans.append(path)
has_find_aim = True
new_queues.append(path)

queues = new_queues

if has_find_aim: # 判断是否已经找到目标词
break

return ans