leetcode_126_127_单词接龙

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))

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值