126. Word Ladder II (python) 单广/ 双广

126. Word Ladder II

Hard

Given two words (beginWord and endWord), and a dictionary's word list, find all shortest transformation sequence(s) from beginWord to endWord, such that:

  1. Only one letter can be changed at a time
  2. Each transformed word must exist in the word list. Note that beginWord is not a transformed word.

Note:

  • Return an empty list if there is no such transformation sequence.
  • All words have the same length.
  • All words contain only lowercase alphabetic characters.
  • You may assume no duplicates in the word list.
  • You may assume beginWord and endWord are non-empty and are not the same.

Example 1:

Input:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]

Output:
[
  ["hit","hot","dot","dog","cog"],
  ["hit","hot","lot","log","cog"]
]

Example 2:

Input:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]

Output: []

Explanation: The endWord "cog" is not in wordList, therefore no possible transformation.

 

笔记:

参考:https://www.youtube.com/watch?v=PblfQrdWXQ4

方法1:先使用广搜的方法,搭建起从beginWord到endWord的树。一次性处理完一整层以后,才跳到下一层去,当访问到了endWord的时候,标记一下找到了endWord,此时当遍历完这一层以后,下一层就不需要遍历了,因为即便其他路径能找到endWord,但是路径的长度肯定不是最短的了。当遍历当前层的时候,用一个next_layer_set去记录下一层的节点有哪些,同时记录当前层的节点的子节点是谁,也就是children这个字典。在当前层遍历的时候,新生成的节点不能是之前路径的节点或者当前层的任意一个节点,所以在处理完一层的时候,需要删除word_set里面当前层的所有节点。一个节点可能有多个children,当某个节点的下一个子节点是endWord的时候,这个节点的所有子节点清空,然后只保留endWord,因为这个节点如果再通过其他的子节点到达endWord的时候,路径肯定不是最短的了。

在创建完这课树以后,从beginWord开始使用dfs去得到路径。

class Solution:
    def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]:
        word_set = set(wordList)
        if endWord not in word_set:
            return []

        children = {}
        same_layer_set = set([])
        same_layer_set.add(beginWord)
        next_layer_set = set([])
        rlt = []
        find = False

        while same_layer_set and not find:
            for word in same_layer_set:
                if word in word_set:
                    word_set.remove(word)
            for current_word in same_layer_set:
                p = current_word
                for j in range(len(beginWord)):
                    for c in "abcdefghijklmnopqrstuvwxyz":
                        new_word = p[:j] + c + p[j + 1:]
                        if new_word in word_set:
                            if not children.get(p):
                                children[p] = []
                            next_layer_set.add(new_word)
                            children[p].append(new_word)
                            if new_word == endWord:
                                find = True
                                children[p] = [endWord]
            same_layer_set = next_layer_set
            next_layer_set = set([])

        if find:
            #print(children)
            def get_path(parent, path):
                if parent == endWord:
                    path.append(endWord)
                    rlt.append(copy.deepcopy(path))
                    path.pop()
                    return
                if not children.get(parent):
                    return

                path.append(parent)
                for child in children[parent]:
                    get_path(child, path)
                path.pop()
            get_path(beginWord, [])
            return rlt

 

方法2:

该方法与方法1的思路基本相同,不同的是,方法1维护的是当前节点的子节点是谁,而方法2维护的则是当前节点的父节点是谁。最后找路径的时候同样是使用dfs,不过是从endWord开始倒着往前找,一直到beginWord。

class Solution:
    def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]:
        word_set = set(wordList)
        if endWord not in word_set:
            return []

        steps = {beginWord: 1}
        parents = {}
        q = collections.deque()
        q.append(beginWord)

        rlt = []

        l = len(beginWord)
        step = 0
        find = False

        while q and not find:
            step += 1
            q_length = len(q)
            for i in range(q_length):
                p = q.popleft()
                if p in word_set:
                    word_set.remove(p)
                for j in range(l):
                    for c in "abcdefghijklmnopqrstuvwxyz":
                        new_word = p[:j] + c + p[j + 1:]
                        if new_word == endWord:
                            find = True
                            if not parents.get(endWord):
                                parents[endWord] = []
                            parents[endWord].append(p)
                        else:
                            if new_word in word_set:
                                if not parents.get(new_word):
                                    parents[new_word] = []
                                    q.append(new_word)
                                    steps[new_word] = step
                                if step <= steps[new_word]:
                                   parents[new_word].append(p)
            step += 1

        if find:
            def get_path(word, path):
                if not parents.get(word):
                    path.append(word)
                    temp_path = copy.deepcopy(path)
                    temp_path.reverse()
                    rlt.append(temp_path)
                    path.pop()
                    return

                path.append(word)
                for temp_word in parents[word]:
                    get_path(temp_word, path)
                path.pop()
            get_path(endWord, [])
            return rlt

 

方法3:使用双广(从两边向中间搜索)加速

假设一个满二叉树的层数是n,如果使用单向广搜,那么效率为O(2^n),如果使用双广的话,两个方向的搜索层数减半,那么效率为2*O(2^(n/2)),也就是指数变为了一半。

这个方法的基本思想跟前面的两个是一样的,但是是从两个方向开始的。该方法在leeCode上的运行时间为116ms,而前两种在650ms左右,效率提高了很多。

这里python的小知识点,python里面 的swap可以直接这样做:a, b = b, a

class Solution:
    def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]:
        word_set = set(wordList)
        if endWord not in word_set:
            return []
        # 用来存储每个word的子节点是谁 比如children["hot"] = ["dot", "lot"]
        children = {}
        # 是否找到了最短路径
        found = False
        rlt = []
        # backward 如果是False代表从beginWord向endWord方向扩展,如果是True,则相反
        backward = False
        # q1代表节点更少的那一边的set,q2代表另一边
        q1 = set([beginWord])
        q2 = set([endWord])
        # 用来存放这一层扩展出来的新的节点
        next_layer_set = set([])

        while q1 and q2 and not found:
            # 每次都选择搜索节点最少的那一边,为了最大化双广的效率,两边各自扩展的节点是一样的时候,效率最大
            if len(q1) > len(q2):
                q1, q2 = q2, q1
                backward = not backward
            # 当要去扩展这一层的子节点的时候,之前已经放到路径里面的节点不再访问,所以需要删去
            for word in q1:
                if word in word_set:
                    word_set.remove(word)
            for word in q2:
                if word in word_set:
                    word_set.remove(word)
            # 拓展q1的所有子节点
            for current_word in q1:
                for j in range(len(current_word)):
                    for c in "abcdefghijklmnopqrstuvwxyz":
                        new_word = current_word[:j] + c + current_word[j + 1:]
                        # parent用来表示父节点,child代表子节点,规定了从beginWord向endWord方向为parent到childre
                        parent, child = current_word, new_word
                        # 这里为了简化写代码的量,我们根据backward决定新扩展的节点是父节点,还是子节点
                        # 如果是从beginWord到endWord,则parent是current_word, child是new_word,反之反过来
                        if backward:
                            parent, child = child, parent
                        # 如果新扩展出来的节点在另一个set里面,则说明两个节点相遇了,那么就找了最短路径
                        if new_word in q2:
                            found = True
                            if not children.get(parent):
                                children[parent] = []
                            children[parent].append(child)
                        else:
                            if new_word in word_set and not found:
                                # 如果这个新扩展出来的节点不在word_set里面,则说明下一层的可扩展节点,然后放到存放下一层节点的set里
                                next_layer_set.add(new_word)
                                if not children.get(parent):
                                    children[parent] = []
                                children[parent].append(child)
            # 这里将下一层要扩展的节点放到当前set里面,同时当前set要先清空,交换完以后,next_layer_set清空,留着下一次用
            # 这里python的swap可以直接如下使用
            q1.clear()
            q1, next_layer_set = next_layer_set, q1
        # dfs找到所有的路径
        if found:
            def get_path(parent, path):
                if parent == endWord:
                    path.append(endWord)
                    rlt.append(copy.deepcopy(path))
                    path.pop()
                    return
                if not children.get(parent):
                    return

                path.append(parent)
                for child in children[parent]:
                    get_path(child, path)
                path.pop()
            get_path(beginWord, [])
            return rlt

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值