最先想出的方法的时间复杂度是 O ( n 2 l ) O(n^2l) O(n2l)的,其中n是wordList长度,l是单词长度,果然超时了。
import queue
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
lWord = len(beginWord)
lList = len(wordList)
# 连接状况
Graph = [[-1 for i in range(lList+1)] for j in range(lList+1)]
# 是否已经访问过某个单词节点
vis = [0 for i in range(lList+1)]
# 到某个位置的路径长度
le = [0 for i in range(lList+1)]
# 将beginWord放到WordList中,方便取值
wordList.append(beginWord)
def existPath(ind1, ind2):
if Graph[ind1][ind2] != -1:
return Graph[ind1][ind2]
else:
word1 = wordList[ind1]
word2 = wordList[ind2]
difcount = 0
for i in range(lWord):
if word1[i] != word2[i]:
difcount += 1
if difcount > 1:
break
if difcount == 1:
Graph[ind1][ind2] = 1
Graph[ind2][ind1] = 1
return 1
else:
Graph[ind1][ind2] = 0
Graph[ind2][ind1] = 0
return 0
result = 0
q = queue.Queue()
vis[lList] = 1
q.put(lList)
le[lList] = 1
while not q.empty():
now = q.get()
for i in range(lList):
if vis[i] != 0:
continue
if existPath(now, i):
vis[i] = 1
le[i] = le[now]+1
# print(wordList[i], le[i])
if wordList[i] == endWord:
result = le[i]
break
q.put(i)
return result
之后看题解,发现可以用添加虚拟节点的方式来使得时间复杂度变为
O
(
n
l
)
O(nl)
O(nl)。添加虚拟节点的方式主要是把当前单词能转移到的位置全部添加上虚拟节点。比如单词"abc",那么可以转移到的位置是"*bc", “a*c"和"ab*”,那么就创建这三个虚拟节点即可。若两个单词之间有路径,那两个单词一定会连到同一个虚拟节点,就不用再挨个比对任意两个单词是否相连通了。时间复杂度变为了先构建所有虚拟节点和单词到虚拟节点的边,
O
(
n
l
)
O(nl)
O(nl),之后再用BFS找到从初始节点到结束节点的路径,所有节点最多弹出或弹入队列一次,所以为
O
(
n
l
)
O(nl)
O(nl),整体的复杂度为
O
(
n
l
)
O(nl)
O(nl)。
需要注意最后的结果为(边数//2+1),因为虚拟节点的引入,使得边数变为之前的两倍,而题目所求的其实是节点个数,节点个数为边数加1。
需要学习的就是C++和python的可变节点数邻接表创建。
C++11
vector<vector<int> > edge;
edge.emplace_back(); // 可创建新节点
edge[id1].push_back(id2); // 直接用索引获取新节点
python3
edge = collections.defaultdict(list)
edge[id1].append(id2)
还有nonlocal修饰符。
num = 0
def f(x):
nonlocal num
a = num
解题代码如下:
import queue
import collections
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
lWord = len(beginWord)
lList = len(wordList)
# 邻接表
edge = collections.defaultdict(list)
# 单词和索引对应的字典
Index = {}
# 当前index
now_index = 0
# 构建单词
def AddWord(word):
nonlocal now_index
if word not in Index.keys():
Index[word] = now_index
now_index += 1
return Index[word]
# 添加到虚拟节点的边
def AddEdge(word):
id1 = AddWord(word)
for i in range(lWord):
virWord = word[:i]+'*'+word[i+1:]
# virWord[i] = '*'
id2 = AddWord(virWord)
edge[id1].append(id2)
edge[id2].append(id1)
begInd = AddWord(beginWord)
endInd = AddWord(endWord)
AddEdge(beginWord)
for i in range(lList):
AddEdge(wordList[i])
# now_index为所有节点个数
dis = [-1 for i in range(now_index)]
result = 0
q = queue.Queue()
q.put(begInd)
dis[begInd] = 0
while not q.empty():
now = q.get()
if now == endInd:
# 总共的边数为dis[now]//2,题目所要的节点数为边数+1
result = dis[now]//2+1
break
for ind in edge[now]:
if dis[ind] == -1:
dis[ind] = dis[now]+1
q.put(ind)
return result