127.单词接龙
描述
中等
给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出: 5
解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",
返回它的长度 5。
示例 2:
输入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
输出: 0
解释: endWord "cog" 不在字典中,所以无法进行转换。
解题
首先借助图的思想建立图
将单词中的某一字母换成"*",表示可以为任何字母,比如"hit"可以转化为"*it"、“h*t"和"hi*”
在"h*t"模式中包含了"hit"和"hot",那么可以认为这两个单词之间存在一条边
即只要两个单词相差一个字母,那么这两个单词之间就存在边
单词之间存在边,可以认为是邻居节点
建立如下的图
然后就可以广度优先遍历来获得开始单词和结束单词之间的距离
算法流程
具体看代码
- 首先进行数据的预处理,记录每种模式下的各种单词,比如"h*t"包括了"hit"和"hot"。以模式为键,单词为值建立字典
- 建立单词层数的记录,比如以"hit"为开始单词,为第0层;"hot"为"hit"的邻近词,为第1层。并且也可以用于判断某单词是否遍历过,遍历到某单词,则在该记录中记录该单词的层数
- 建立队列对各单词进行层级的访问
- 取出队列第一个节点,如果该节点的邻居节点没有层数的记录,则记录为下一层的节点,并加到队伍末尾
- 当遍历到结束节点时,返回记录中的节点层数+1
from collections import defaultdict
from typing import List
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
if endWord not in wordList:
return 0
length = len(beginWord)
# 用于保存同一种模式下的单词信息
dic = defaultdict(set)
for word in wordList:
for i in range(length):
dic[word[:i] + "*" + word[i + 1:]].add(word)
# 记录单词层数
word_level = {beginWord: 0}
queue = [beginWord]
while queue:
cur = queue.pop(0)
# 遍历该单词的不同模式
for i in range(length):
# 遍历当前单词的邻居节点
for word in dic[cur[:i] + "*" + cur[i + 1:]]:
# 添加单词的层数记录
if word not in word_level:
word_level[word] = word_level[cur] + 1
queue.append(word)
# 返回结果
if word == endWord:
return word_level[word] + 1
return 0
广度优先遍历有一特点,通常情况下,每一层的节点会比上一次的节点数要多,有着指数爆炸的可能性
一边从起始节点开始向后查找,一边从结束节点开始向前查找
分别建立从起始节点开始查找的节点层数的记录,和从结束节点开始查找的节点层数记录
直到某一节点同时出现在两个记录中,说明找到了一条路径,将两个记录的层数相加即可
class Solution:
def __init__(self):
self.length = 0
self.dic = defaultdict(set)
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
if endWord not in wordList:
return 0
self.length = len(beginWord)
for word in wordList:
for i in range(self.length):
self.dic[word[:i] + "*" + word[i + 1:]].add(word)
word_level_begin = {beginWord: 0}
queue_begin = [beginWord]
word_level_end = {endWord: 0}
queue_end = [endWord]
while queue_begin and queue_end:
ans = self.visit(queue_begin, word_level_begin, word_level_end)
if ans:
return ans
ans = self.visit(queue_end, word_level_end, word_level_begin)
if ans:
return ans
return 0
def visit(self, queue, word_level, other_word_level):
cur = queue.pop(0)
for i in range(self.length):
for word in self.dic[cur[:i] + "*" + cur[i + 1:]]:
if word not in word_level:
word_level[word] = word_level[cur] + 1
queue.append(word)
if word in other_word_level:
return word_level[word] + other_word_level[word] + 1
return None
126.单词接龙II
描述
困难
给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:
每次转换只能改变一个字母。
转换后得到的单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回一个空列表。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出:
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]
示例 2:
输入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
输出: []
解释: endWord "cog" 不在字典中,所以不存在符合要求的转换序列。
解题
这一题在最短序列的基础上,输出这些序列
很容易想到深度优先遍历
借助第127题第一种方式得到了不同节点所在层数记录,来实现DFS的层数变化
from collections import defaultdict
from typing import List
class Solution(object):
def __init__(self):
self.res = []
self.dic = defaultdict(set) # 保存同种模式下的单词信息
self.length = 0 # 每个单词长度
self.word_level = {} # 记录各单词节点层数
def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]:
if endWord not in wordList:
return []
self.length = len(beginWord)
for word in wordList:
for i in range(self.length):
self.dic[word[:i] + "*" + word[i + 1:]].add(word)
self.bfs(beginWord)
self.dfs(beginWord, endWord, [beginWord])
return self.res
# 记录单词层数
def bfs(self, beginWord):
self.word_level[beginWord] = 0
queue = [beginWord]
while queue:
cur = queue.pop(0)
for word in self.get_next_words(cur):
if word not in self.word_level:
self.word_level[word] = self.word_level[cur] + 1
queue.append(word)
# 回溯查找
def dfs(self, cur, target, temp):
if cur == target:
self.res.append(temp[:])
return
for word in self.get_next_words(cur):
# 如果当前单词cur的下一层不包含该邻居节点word,直接跳过
# 由于没有设置visit集合来标记是否已经遍历过,word可能为cur上一层节点
if self.word_level[cur] + 1 != self.word_level[word]:
continue
temp.append(word)
self.dfs(word, target, temp)
temp.pop()
# 获取邻居节点
def get_next_words(self, cur):
words = []
for i in range(self.length):
for word in self.dic[cur[:i] + "*" + cur[i + 1:]]:
words.append(word)
return words
if __name__ == '__main__':
beginWord = "hit"
endWord = "cog"
wordList = ["hot", "dot", "dog", "lot", "log", "cog"]
print(Solution().findLadders(beginWord, endWord, wordList))