【二叉树】(三) 总结

目录

一、从中序与后序遍历序列构造二叉树

1.1 题目要求

1.2 解决过程

二、从前序与中序遍历序列构造二叉树

2.1 题目要求

2.2 解决过程

三、填充每个节点的下一个右侧节点指针

3.1 题目要求

3.2 解决过程

四、填充每个节点的下一个右侧节点指针 II

4.1 题目要求

4.2 解决过程

五、二叉树的最近公共祖先

5.1 题目要求

5.2 解决过程

六、二叉树的序列化与反序列化

6.1 题目要求

6.2 解决过程


一、从中序与后序遍历序列构造二叉树

1.1 题目要求

1.2 解决过程

官方实现与说明


class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
        def helper(in_left, in_right):
            # helper 参数分别为中序序列中当前子树的左右边界
            # if there is no elements to construct subtrees
            if in_left > in_right:  # 到达叶子节点
                return None
            
            # pick up the last element of postorder seq as a root
            val = postorder.pop()  # 后序序列的最后一个节点 = 中序序列当前子树根节点
            root = TreeNode(val)

            # root splits inorder list into left and right subtrees
            index = idx_map[val]  # 中序序列当前子树根节点在序列中的索引
 
            # build right subtree
            root.right = helper(index + 1, in_right)  # 划定范围构造右子树(必须先)
            # build left subtree
            root.left = helper(in_left, index - 1)  # 划定范围构造左子树(必须后)
            return root  # 返回当前 helper 的节点
        
        # build a hashmap of inorder seq:  value -> its index
        idx_map = {val:idx for idx, val in enumerate(inorder)}  # 中序序列字典
        return helper(0, len(inorder) - 1)

2020/07/27 - 89.75% (56ms)

参考文献

https://leetcode-cn.com/explore/learn/card/data-structure-binary-tree/4/conclusion/15/

https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/solution/cong-zhong-xu-yu-hou-xu-bian-li-xu-lie-gou-zao-e-5/


二、从前序与中序遍历序列构造二叉树

2.1 题目要求

2.2 解决过程

个人实现

法一:尾递归。自上题按图索骥实现。

2020/07/27 - 99.94% - 最佳

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        def helper(in_left, in_right):
            if in_left > in_right:          # 叶子节点
                return None
            # 处理当前子树根节点相关
            value = preorder.pop(0)         # 当前子树根节点元素值
            root = TreeNode(value)          # 当前子树根节点
            index = hashmap[value]          # 当前子树根节点在中序序列中的索引
            # 分别按顺序构造左、右子树
            root.left = helper(in_left, index-1)   # 左子树先
            root.right = helper(index+1, in_right) # 右子树后
            # 返回当前子树根节点用于链接
            return root
        
        # 构造中序遍历哈希表用于按值取索引         
        hashmap = {val:idx for idx, val in enumerate(inorder)}  
        return helper(0, len(inorder)-1)

官方实现与说明


# 没有个人实现简洁明了
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        def myBuildTree(preorder_left: int, preorder_right: int, inorder_left: int, inorder_right: int):
            if preorder_left > preorder_right:
                return None
            
            # 前序遍历中的第一个节点就是根节点
            preorder_root = preorder_left
            # 在中序遍历中定位根节点
            inorder_root = index[preorder[preorder_root]]
            
            # 先把根节点建立出来
            root = TreeNode(preorder[preorder_root])
            # 得到左子树中的节点数目
            size_left_subtree = inorder_root - inorder_left
            # 递归地构造左子树,并连接到根节点
            # 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
            root.left = myBuildTree(preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1)
            # 递归地构造右子树,并连接到根节点
            # 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
            root.right = myBuildTree(preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right)
            return root
        
        n = len(preorder)
        # 构造哈希映射,帮助我们快速定位根节点
        index = {element: i for i, element in enumerate(inorder)}
        return myBuildTree(0, n - 1, 0, n - 1)


class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        if not preorder:
            return None

        root = TreeNode(preorder[0])
        stack = [root]
        inorderIndex = 0
        for i in range(1, len(preorder)):
            preorderVal = preorder[i]
            node = stack[-1]
            if node.val != inorder[inorderIndex]:
                node.left = TreeNode(preorderVal)
                stack.append(node.left)
            else:
                while stack and stack[-1].val == inorder[inorderIndex]:
                    node = stack.pop()
                    inorderIndex += 1
                node.right = TreeNode(preorderVal)
                stack.append(node.right)

        return root

 参考文献

https://leetcode-cn.com/explore/learn/card/data-structure-binary-tree/4/conclusion/16/

https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/solution/cong-qian-xu-yu-zhong-xu-bian-li-xu-lie-gou-zao-9/


三、填充每个节点的下一个右侧节点指针

3.1 题目要求

 

3.2 解决过程

个人实现

法一:广度优先搜索 (BFS)。使用一个双端队列 Q 来辅助存储当前层节点 (从第二层开始)。只要 Q 中还有节点,就继续进行 BFS,而每轮循环都处理一层节点的 next 链接。在一轮循环中,首先计算 Q 的长度 nums 作为当前层节点数,然后令首个节点 cur 出队,令 cur 的孩子节点 (如果有) 入队。接着,依次取出当前层其余节点,并同理令各节点的孩子节点入队,作为下一层节点。注意,nums - 1 次 for 循环使得当前层节点不会与新入队的下一层节点混淆。最后,依次令 cur 指向后续节点 nxt,完成当前层 next 链接。空间复杂度 O(n),时间复杂度 O(n)。

2020/07/27 - 93.25 (75ms) - 不错

"""
# Definition for a Node.
class Node:
    def __init__(self, val: int = 0, left: 'Node' = None, right: 'Node' = None, next: 'Node' = None):
        self.val = val
        self.left = left
        self.right = right
        self.next = next
"""

class Solution:
    def connect(self, root: 'Node') -> 'Node':
        if not root:                     # 特殊情况 - 空树
            return root
        
        Q = collections.deque()          # 当前层与下一层节点存储双端队列
        if root.left:
            Q.append(root.left)
        if root.right:
            Q.append(root.right)      

        while Q:                         # 只要双堆队列中还有节点要处理
            nums = len(Q)                # 当前层节点数
            if nums > 1:                 # 如果当前层只有一个节点, 默认有 next=None, 无需处理
                cur = Q.popleft()        # 当前层首个节点
                if cur.left:
                    Q.append(cur.left)
                if cur.right:
                    Q.append(cur.right)
            
                for _ in range(nums-1):  # 当前层其余节点
                    nxt = Q.popleft()    # 下一个节点 nxt
                    if nxt.left:
                        Q.append(nxt.left)
                    if nxt.right:
                        Q.append(nxt.right)
                    cur.next = nxt       # 当前节点 cur 指向下一个节点 nxt
                    cur = cur.next       # 当前节点后移
        return root

法一改:BFS 实现优化。似乎更慢了?

2020/07/27 - 69.41% (80ms)

class Solution:
    def addnode(self, node, queue):
        " 令节点 node 的孩子节点 child node 依次入队 queue"
        if node.left:
            queue.append(node.left)
        if node.right:
            queue.append(node.right)
    
    def connect(self, root: 'Node') -> 'Node':
        if not root:                     # 特殊情况 - 空树
            return root
        
        Q = collections.deque()          # 当前层与下一层节点存储双端队列 Q
        self.addnode(root, Q)            # 根节点 root 的孩子节点入队

        while Q:                         # 只要双堆队列中还有节点要处理
            nums = len(Q)                # 当前层节点数 nums
            if nums > 1:                 # 如果当前层只有一个节点, 默认有 next=None, 无需处理
                cur = Q.popleft()        # 当前层首个节点 cur
                self.addnode(cur, Q)     # 首个节点 cur 的孩子节点入队
                for _ in range(nums-1):  # 当前层其余节点(共 nums-1 个)
                    nxt = Q.popleft()    # 下一节点 nxt
                    self.addnode(nxt, Q) # 下一节点 nxt 的孩子节点入队
                    cur.next = nxt       # 当前节点 cur 指向下一节点 nxt
                    cur = cur.next       # 当前节点后移
        return root

法一改改:BFS 实现再优化。把辅助函数 _addnode() (其实应叫 addchildren) 非公有内嵌 至  connect() 似乎更好!

2020/07/27 - 93.25 (75ms) - 不错

class Solution:
    def connect(self, root: 'Node') -> 'Node':
        if not root:                    # 特殊情况 - 空树
            return root
        
        def _addnode(_node, _queue):
            " non-public nested function"
            if _node.left:
                _queue.append(_node.left)
            if _node.right:
                _queue.append(_node.right)

        Q = collections.deque()         # 当前层与下一层节点存储双端队列 Q
        Q.append(root)                  # 加入根节点 root 先入队
        
        while Q:                        # 只要双堆队列中还有节点要处理
            nums = len(Q)               # 当前层节点数 nums (完美二叉树一定有子节点)
            cur = Q.popleft()           # 当前层首个节点 cur
            _addnode(cur, Q)            # 首个节点 cur 的孩子节点入队
            for _ in range(nums-1):     # 当前层其余节点(共 nums-1 个)
                nxt = Q.popleft()       # 下一节点 nxt
                _addnode(nxt, Q)        # 下一节点 nxt 的孩子节点入队
                cur.next = nxt          # 当前节点 cur 指向下一节点 nxt
                cur = cur.next          # 当前节点后移
        return root

官方实现与说明

# 同个人实现所用的思想
while (!Q.empty())
    size = Q.size()
    for i in range 0..size
        node = Q.pop()
        Q.push(node.left)
        Q.push(node.right)

import collections 

class Solution:
    def connect(self, root: 'Node') -> 'Node':
        if not root:
            return root
        
        # Initialize a queue data structure which contains just the root of the tree
        Q = collections.deque([root])
        
        # Outer while loop which iterates over each level
        while Q:

            # Note the size of the queue
            size = len(Q)
            
            # Iterate over all the nodes on the current level
            for i in range(size):
                
                # Pop a node from the front of the queue
                node = Q.popleft()
                
                # This check is important. We don't want to establish any wrong connections.
                # The queue will contain nodes from 2 levels at most at any point in time. 
                # This check ensures we only don't establish next pointers beyond the end of a level
                if i < size - 1:
                    node.next = Q[0]  # 这就过分了, 竟然还可以这样?!
                
                # Add the children, if any, to the back of the queue
                if node.left:
                    Q.append(node.left)
                if node.right:
                    Q.append(node.right)
        
        # Since the tree has now been modified, return the root node
        return root


## 该方法也很巧妙!
# Python implementation
class Solution:
    def connect(self, root: 'Node') -> 'Node':
        if not root:
            return root
        
        # Start with the root node. There are no next 
        # pointers that need to be set up on the first level
        leftmost = root
        
        # Once we reach the final level, we are done
        while leftmost.left:
            
            # Iterate the "linked list" starting from the head
            # node and using the next pointers, establish the 
            # corresponding links for the next level
            head = leftmost
            while head:
                
                # CONNECTION 1
                head.left.next = head.right  # 同父节点的左 → 右子节点连接
                
                # CONNECTION 2
                if head.next:
                    head.right.next = head.next.left  # 异父节点的右 → 左 子节点连接
                
                # Progress along the list (nodes on the current level)
                head = head.next  # 在 N-1 层后移父节点
            
            # Move onto the next level
            leftmost = leftmost.left  # 在 N 层重新找到首个父节点
        
        return root

2020/07/28 - 93.14% (72ms) 

参考文献

https://leetcode-cn.com/explore/learn/card/data-structure-binary-tree/4/conclusion/17/

https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/solution/tian-chong-mei-ge-jie-dian-de-xia-yi-ge-you-ce-j-3/


 

四、填充每个节点的下一个右侧节点指针 II

4.1 题目要求

4.2 解决过程

树节点定义

"""
# Definition for a Node.
class Node:
    def __init__(self, val: int = 0, left: 'Node' = None, right: 'Node' = None, next: 'Node' = None):
        self.val = val
        self.left = left
        self.right = right
        self.next = next
"""

个人实现

法一:广度优先搜索 (BFS)。实现方式完全同上一题的个人实现。时间复杂度 O(n),空间复杂度 O(n)。

2020/07/29 - 81.56% (60ms)

class Solution:
    def connect(self, root: 'Node') -> 'Node':
        if not root:
            return root
        
        def _addchild(_node, _queue):
            """ non-public nested function"""
            if _node.left:
                _queue.append(_node.left)
            if _node.right:
                _queue.append(_node.right)
         
        Q = collections.deque()     # 辅助双端队列
        Q.append(root)              # 根节点入队
        
        while Q:
            nums = len(Q)           # 当前层节点数/当前层横向连接处理次数
            cur = Q.popleft()       # 当前层首节点出队
            _addchild(cur, Q)       # 下一层节点入队
            for _ in range(nums-1):
                nxt = Q.popleft()   # 当前层第二个节点出队
                _addchild(nxt, Q)   # 下一层节点入队
                cur.next = nxt
                cur = cur.next
                
        return root

官方实现与说明

import collections 

class Solution:
    def connect(self, root: 'Node') -> 'Node':
        
        if not root:
            return root
        
        # Initialize a queue data structure which contains
        # just the root of the tree
        Q = collections.deque([root])
        
        # Outer while loop which iterates over 
        # each level
        while Q:
            
            # Note the size of the queue
            size = len(Q)
            
            # Iterate over all the nodes on the current level
            for i in range(size):
                
                # Pop a node from the front of the queue
                node = Q.popleft()
                
                # This check is important. We don't want to
                # establish any wrong connections. The queue will
                # contain nodes from 2 levels at most at any
                # point in time. This check ensures we only 
                # don't establish next pointers beyond the end
                # of a level
                if i < size - 1:
                    node.next = Q[0]
                
                # Add the children, if any, to the back of
                # the queue
                if node.left:
                    Q.append(node.left)
                if node.right:
                    Q.append(node.right)
        
        # Since the tree has now been modified, return the root node
        return root


class Solution:
    
    def processChild(self, childNode, prev, leftmost):
        if childNode:  # 如果有孩子节点, 建立连接
            
            # If the "prev" pointer is alread set i.e. if we
            # already found atleast one node on the next level,
            # setup its next pointer
            if prev:
                prev.next = childNode  # 下一层节点建立连接
            else:    
                # Else it means this child node is the first node
                # we have encountered on the next level, so, we
                # set the leftmost pointer
                leftmost = childNode  # 下一层首个节点
            prev = childNode  # 下一层首个节点
        return prev, leftmost
    
    def connect(self, root: 'Node') -> 'Node':
        if not root:
            return root
        
        # The root node is the only node on the first level
        # and hence its the leftmost node for that level
        leftmost = root
        
        # We have no idea about the structure of the tree,
        # so, we keep going until we do find the last level.
        # The nodes on the last level won't have any children
        while leftmost:
            
            # "prev" tracks the latest node on the "next" level while
            # "curr" tracks the latest node on the current level.
            prev, curr = None, leftmost
            
            # We reset this so that we can re-assign it to the leftmost
            # node of the next level. Also, if there isn't one, this
            # would help break us out of the outermost loop.
            leftmost = None
            
            # Iterate on the nodes in the current level 
            # using the next pointers already established.
            while curr:  # 只要当前层还有节点
                
                # Process both the children and update the 
                # prev and leftmost pointers as necessary.
                prev, leftmost = self.processChild(curr.left, prev, leftmost)  # 下一层节点建立连接
                prev, leftmost = self.processChild(curr.right, prev, leftmost)  # 下一层节点建立连接
                
                # Move onto the next node.
                curr = curr.next  # 当前层节点后移
                
        return root 

参考文献

https://leetcode-cn.com/explore/learn/card/data-structure-binary-tree/4/conclusion/18/

https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node-ii/solution/tian-chong-mei-ge-jie-dian-de-xia-yi-ge-you-ce-j-4/


五、二叉树的最近公共祖先

5.1 题目要求

5.2 解决过程

个人实现

法一:暴力迭代法

规定,根节点索引为最小值 0,设树中任一节点索引为 i,则其左子节点索引为 2*i+1、右子节点索引为 2*i+2,则使用哈希表 hashmap 记录各节点及其索引,并记录节点 p 的索引 p_index 与节点 q 的索引 q_index。

然后,选取 p_index 与 q_index 中的较大者 max_index,设其索引为 c,则其父节点索引必为 (c-1)//2。由此,不断回查父节点,同时与较小者 min_index 比较,并使用集合 trace 记录各父节点索引 (只记录可能匹配的以减少 in 查找时间)。

接着,倘若仍未匹配,则令 min_index 同理回查父节点,并在 trace 中不断查找,直至匹配。

空间复杂度 O(n),时间复杂度 O(nlogn)。

2020/07/29 - 5.54% (168ms) - 可以说是很暴力、很差劲了 ...

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        def recur(root, index, record):
            """ 
            用于记录所有节点及其索引, 设当前节点索引为 i, 
            则其左子节点索引为 2*i+1, 其右子节点索引为 2*i+2
            """
            nonlocal p_index, q_index               # 非局部变量声明
            if not root:
                return
            if root == p:
                p_index = index                     # 找到 p 节点索引
            if root == q:
                q_index = index                     # 找到 q 节点索引
            record[index] = root                    # 记录当前节点
            recur(root.left, index*2+1, record)     # 记录左子节点
            recur(root.right, index*2+2, record)    # 记录右子节点
            
        hashmap = {}                                # 哈希表
        p_index = q_index = index = 0               # p 索引、q 索引、其余节点索引
        recur(root, 0, hashmap)                     # 记录所有节点及其索引 {index:node}
        
        max_index = max(p_index, q_index)           # 较大者索引
        min_index = min(p_index, q_index)           # 较小者索引
        trace = set()                               # 较大节点回查父节点记录
        
        # 从索引较大者开始回查父节点索引
        while max_index >= 0:
            if max_index == min_index:              # 索引匹配, 找到最近公共祖先
                return hashmap[max_index]
            max_index = (max_index-1) // 2          # 计算父节点索引
            trace.add(max_index)                    # 父节点索引记录
            
        # 从索引较小者开始回查父节点索引
        while min_index >= 0:
            if min_index in trace:                  # 索引记录命中, 找到最近公共祖先
                return hashmap[min_index]
            min_index = (min_index-1) // 2          # 计算父节点索引
            
        # 始终无法匹配索引, 最近公共祖先必为根节点
        return root

法一改:优化的迭代法。即便法一是那么的低效,还是能稍稍优化一下:

一是构造 hashmap 时不再记录无用的节点,找到 p_index 和 q_index 即可提前退出递归。

二是调整 while 循环的处理,总令 p_index 和 q_index 中较大的一个回查父节点,从而实现动态更新和比较。

空间复杂度 O(n),时间复杂度 O(n)

2020/07/29 - 5.54% (192ms) - 中间表示的构造占据了大量时间

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        def recur(root, index, record):
            nonlocal p_index, q_index               # 非局部变量声明
            if (not root) or (p_index != 0 and q_index != 0):  # 不再记录无关的节点了
                return
            if root == p:
                p_index = index                     # 找到 p 节点索引
            if root == q:
                q_index = index                     # 找到 q 节点索引
            record[index] = root                    # 记录当前节点
            recur(root.left, index*2+1, record)     # 记录左子节点
            recur(root.right, index*2+2, record)    # 记录右子节点
            
        hashmap = {}                                # 哈希表
        p_index = q_index = index = 0               # p 索引、q 索引、其余节点索引
        recur(root, 0, hashmap)                     # 记录所有节点及其索引 {index:node}
        
        # 循环动态处理
        while (p_index >= 0) and (q_index >= 0):
            if p_index == q_index:                  # 索引匹配, 找到最近公共祖先
                return hashmap[p_index]
            elif p_index > q_index:                 # 索引较大者回查父节点
                p_index = (p_index-1) // 2
            else:
                q_index = (q_index-1) // 2
 
        return root

其他实现与说明

以下动画很重要 (详见参考文献): 

class Solution:
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
        if (not root) or (root == p) or (root == q):  # 只有两种情况:没找到 / 找到 目标节点
            return root
        # left 和 right 保存左、右子树查找结果, 只有两种情况:None 或 目标节点 p / q
        left = self.lowestCommonAncestor(root.left, p, q)  
        right = self.lowestCommonAncestor(root.right, p, q)  
        if not left:  # 如果在左子树中没找到目标节点, 返回右子树查找结果
            return right
        if not right:  # 如果在右子树中没找到目标节点, 返回左子树查找结果
            return left
        return root  # 当 left 和 right 均找到时, 当前 root 即为最近祖先节点

2020/07/29 - 99.27% (72ms) - 最佳

class Solution:
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
        if not root or root == p or root == q: 
            return root

        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)
        if not left and not right: 
            return  # 1.
        if not left: 
            return right  # 3.
        if not right: 
            return left  # 4.

        return root  # 2. if left and right:

参考文献

https://leetcode-cn.com/explore/learn/card/data-structure-binary-tree/4/conclusion/19/

https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/solution/mian-shi-ti-68-ii-er-cha-shu-de-zui-jin-gong-gon-7/


六、二叉树的序列化与反序列化

6.1 题目要求

6.2 解决过程

测试用例

[1,2,3,null,null,4,5]
[5,2,3,null,null,2,4,3,1]
[1,null,2,null,3,null,4,null,5,null,6,null,7,null,8,null,9,null,10,null,11,null,12,null,13,null,14,null,15,null,16,null,17,null,18,null,19,null,20,null,21,null,22,null,23,null,24,null,25,null,26,null,27,null,28,null,29,null,30,null,31,null,32,null,33,null,34,null,35,null,36,null,37,null,38,null,39,null,40,null,41,null,42,null,43,null,44,null,45,null,46,null,47,null,48,null,49,null,50,null,51,null,52,null,53,null,54,null,55,null,56,null,57,null,58,null,59,null,60,null,61,null,62,null,63,null,64,null,65,null,66,null,67,null,68,null,69,null,70,null,71,null,72,null,73,null,74,null,75,null,76,null,77,null,78,null,79,null,80,null,81,null,82,null,83,null,84,null,85,null,86,null,87,null,88,null,89,null,90,null,91,null,92,null,93,null,94,null,95,null,96,null,97,null,98,null,99,null,100,null,101,null,102,null,103,null,104,null,105,null,106,null,107,null,108,null,109,null,110,null,111,null,112,null,113,null,114,null,115,null,116,null,117,null,118,null,119,null,120,null,121,null,122,null,123,null,124,null,125,null,126,null,127,null,128,null,129,null,130,null,131,null,132,null,133,null,134,null,135,null,136,null,137,null,138,null,139,null,140,null,141,null,142,null,143,null,144,null,145,null,146,null,147,null,148,null,149,null,150,null,151,null,152,null,153,null,154,null,155,null,156,null,157,null,158,null,159,null,160,null,161,null,162,null,163,null,164,null,165,null,166,null,167,null,168,null,169,null,170,null,171,null,172,null,173,null,174,null,175,null,176,null,177,null,178,null,179,null,180,null,181,null,182,null,183,null,184,null,185,null,186,null,187,null,188,null,189,null,190,null,191,null,192,null,193,null,194,null,195,null,196,null,197,null,198,null,199,null,200,null,201,null,202,null,203,null,204,null,205,null,206,null,207,null,208,null,209,null,210,null,211,null,212,null,213,null,214,null,215,null,216,null,217,null,218,null,219,null,220,null,221,null,222,null,223,null,224,null,225,null,226,null,227,null,228,null,229,null,230,null,231,null,232,null,233,null,234,null,235,null,236,null,237,null,238,null,239,null,240,null,241,null,242,null,243,null,244,null,245,null,246,null,247,null,248,null,249,null,250,null,251,null,252,null,253,null,254,null,255,null,256,null,257,null,258,null,259,null,260,null,261,null,262,null,263,null,264,null,265,null,266,null,267,null,268,null,269,null,270,null,271,null,272,null,273,null,274,null,275,null,276,null,277,null,278,null,279,null,280,null,281,null,282,null,283,null,284,null,285,null,286,null,287,null,288,null,289,null,290,null,291,null,292,null,293,null,294,null,295,null,296,null,297,null,298,null,299,null,300,null,301,null,302,null,303,null,304,null,305,null,306,null,307,null,308,null,309,null,310,null,311,null,312,null,313,null,314,null,315,null,316,null,317,null,318,null,319,null,320,null,321,null,322,null,323,null,324,null,325,null,326,null,327,null,328,null,329,null,330,null,331,null,332,null,333,null,334,null,335,null,336,null,337,null,338,null,339,null,340,null,341,null,342,null,343,null,344,null,345,null,346,null,347,null,348,null,349,null,350,null,351,null,352,null,353,null,354,null,355,null,356,null,357,null,358,null,359,null,360,null,361,null,362,null,363,null,364,null,365,null,366,null,367,null,368,null,369,null,370,null,371,null,372,null,373,null,374,null,375,null,376,null,377,null,378,null,379,null,380,null,381,null,382,null,383,null,384,null,385,null,386,null,387,null,388,null,389,null,390,null,391,null,392,null,393,null,394,null,395,null,396,null,397,null,398,null,399,null,400,null,401,null,402,null,403,null,404,null,405,null,406,null,407,null,408,null,409,null,410,null,411,null,412,null,413,null,414,null,415,null,416,null,417,null,418,null,419,null,420,null,421,null,422,null,423,null,424,null,425,null,426,null,427,null,428,null,429,null,430,null,431,null,432,null,433,null,434,null,435,null,436,null,437,null,438,null,439,null,440,null,441,null,442,null,443,null,444,null,445,null,446,null,447,null,448,null,449,null,450,null,451,null,452,null,453,null,454,null,455,null,456,null,457,null,458,null,459,null,460,null,461,null,462,null,463,null,464,null,465,null,466,null,467,null,468,null,469,null,470,null,471,null,472,null,473,null,474,null,475,null,476,null,477,null,478,null,479,null,480,null,481,null,482,null,483,null,484,null,485,null,486,null,487,null,488,null,489,null,490,null,491,null,492,null,493,null,494,null,495,null,496,null,497,null,498,null,499,null,500,null,501,null,502,null,503,null,504,null,505,null,506,null,507,null,508,null,509,null,510,null,511,null,512,null,513,null,514,null,515,null,516,null,517,null,518,null,519,null,520,null,521,null,522,null,523,null,524,null,525,null,526,null,527,null,528,null,529,null,530,null,531,null,532,null,533,null,534,null,535,null,536,null,537,null,538,null,539,null,540,null,541,null,542,null,543,null,544,null,545,null,546,null,547,null,548,null,549,null,550,null,551,null,552,null,553,null,554,null,555,null,556,null,557,null,558,null,559,null,560,null,561,null,562,null,563,null,564,null,565,null,566,null,567,null,568,null,569,null,570,null,571,null,572,null,573,null,574,null,575,null,576,null,577,null,578,null,579,null,580,null,581,null,582,null,583,null,584,null,585,null,586,null,587,null,588,null,589,null,590,null,591,null,592,null,593,null,594,null,595,null,596,null,597,null,598,null,599,null,600,null,601,null,602,null,603,null,604,null,605,null,606,null,607,null,608,null,609,null,610,null,611,null,612,null,613,null,614,null,615,null,616,null,617,null,618,null,619,null,620,null,621,null,622,null,623,null,624,null,625,null,626,null,627,null,628,null,629,null,630,null,631,null,632,null,633,null,634,null,635,null,636,null,637,null,638,null,639,null,640,null,641,null,642,null,643,null,644,null,645,null,646,null,647,null,648,null,649,null,650,null,651,null,652,null,653,null,654,null,655,null,656,null,657,null,658,null,659,null,660,null,661,null,662,null,663,null,664,null,665,null,666,null,667,null,668,null,669,null,670,null,671,null,672,null,673,null,674,null,675,null,676,null,677,null,678,null,679,null,680,null,681,null,682,null,683,null,684,null,685,null,686,null,687,null,688,null,689,null,690,null,691,null,692,null,693,null,694,null,695,null,696,null,697,null,698,null,699,null,700,null,701,null,702,null,703,null,704,null,705,null,706,null,707,null,708,null,709,null,710,null,711,null,712,null,713,null,714,null,715,null,716,null,717,null,718,null,719,null,720,null,721,null,722,null,723,null,724,null,725,null,726,null,727,null,728,null,729,null,730,null,731,null,732,null,733,null,734,null,735,null,736,null,737,null,738,null,739,null,740,null,741,null,742,null,743,null,744,null,745,null,746,null,747,null,748,null,749,null,750,null,751,null,752,null,753,null,754,null,755,null,756,null,757,null,758,null,759,null,760,null,761,null,762,null,763,null,764,null,765,null,766,null,767,null,768,null,769,null,770,null,771,null,772,null,773,null,774,null,775,null,776,null,777,null,778,null,779,null,780,null,781,null,782,null,783,null,784,null,785,null,786,null,787,null,788,null,789,null,790,null,791,null,792,null,793,null,794,null,795,null,796,null,797,null,798,null,799,null,800,null,801,null,802,null,803,null,804,null,805,null,806,null,807,null,808,null,809,null,810,null,811,null,812,null,813,null,814,null,815,null,816,null,817,null,818,null,819,null,820,null,821,null,822,null,823,null,824,null,825,null,826,null,827,null,828,null,829,null,830,null,831,null,832,null,833,null,834,null,835,null,836,null,837,null,838,null,839,null,840,null,841,null,842,null,843,null,844,null,845,null,846,null,847,null,848,null,849,null,850,null,851,null,852,null,853,null,854,null,855,null,856,null,857,null,858,null,859,null,860,null,861,null,862,null,863,null,864,null,865,null,866,null,867,null,868,null,869,null,870,null,871,null,872,null,873,null,874,null,875,null,876,null,877,null,878,null,879,null,880,null,881,null,882,null,883,null,884,null,885,null,886,null,887,null,888,null,889,null,890,null,891,null,892,null,893,null,894,null,895,null,896,null,897,null,898,null,899,null,900,null,901,null,902,null,903,null,904,null,905,null,906,null,907,null,908,null,909,null,910,null,911,null,912,null,913,null,914,null,915,null,916,null,917,null,918,null,919,null,920,null,921,null,922,null,923,null,924,null,925,null,926,null,927,null,928,null,929,null,930,null,931,null,932,null,933,null,934,null,935,null,936,null,937,null,938,null,939,null,940,null,941,null,942,null,943,null,944,null,945,null,946,null,947,null,948,null,949,null,950,null,951,null,952,null,953,null,954,null,955,null,956,null,957,null,958,null,959,null,960,null,961,null,962,null,963,null,964,null,965,null,966,null,967,null,968,null,969,null,970,null,971,null,972,null,973,null,974,null,975,null,976,null,977,null,978,null,979,null,980,null,981,null,982,null,983,null,984,null,985,null,986,null,987,null,988,null,989,null,990,null,991,null,992,null,993,null,994,null,995,null,996,null,997,null,998,null,999,null,1000]

失败测试

法一:BFS 实现编码 + 列表 (中间形式) + DFS (preorder) 递归实现解码

使用 BFS + 辅助双端队列实现序列化,其中每轮循环是否进行取决于当前层是否存在有效节点 (Flag);使用递归实现反序列化,其中字符串类型的序列化数据通过 eval() 函数转换为列表类型,而不应使用 list() 函数。

然而,最后超时了,原因是一个超长单支树导致了大量的时间和空间占用。

2020/07/29 - 超出时间限制 - 或许使用完全纪实的列表并非一种好的中间形式!

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Codec:
    def serialize(self, root):
        """ Encodes a tree to a single string. (BFS)
        
        type root: TreeNode
        rtype: str
        """
        temp = []
        layer = -1
        Flag = True
        
        Q = collections.deque()
        Q.append(root)
        
        while Flag:
            Flag = False                        # 假定下一层没有有效节点
            layer += 1    
            for _ in range(2**layer):           # 当前层节点数 1、2、4、8
                cur = Q.popleft()               # 当前层的一个节点 cur
                # 如果 cur 是有效节点
                if cur:                         
                    temp.append(cur.val)
                    # 左子节点处理
                    if cur.left:
                        Q.append(cur.left)      # 子节点入队
                        Flag = True             # 证明下一层还有有效节点
                    else:
                        Q.append(None)          # 空节点入队占位
                    # 右子节点处理
                    if cur.right:
                        Q.append(cur.right)     # 子节点入队
                        Flag = True             # 证明下一层还有有效节点
                    else:
                        Q.append(None)          # 空节点入队占位
                # 否则 cur 为无效节点
                else:                           
                    temp.append(None)
                    Q.append(None)              # None 占位左子节点入队
                    Q.append(None)              # None 占位右子节点入队

        return str(temp)


    def deserialize(self, data):
        """ Decodes your encoded data to tree. (recursion)
        
        type data: str
        rtype: TreeNode
        """
        lst = eval(data)  # str → list
        nums = len(lst)   # 节点总数

        def recur(index, limit):
            if (index >= limit) or (lst[index] == None):
                return None
            root = TreeNode(lst[index])             # 创建当前节点
            root.left = recur(index*2+1, limit)     # 指向左子节点
            root.right = recur(index*2+2, limit)    # 指向右子节点
            return root
        
        return recur(0, nums)
        
# Your Codec object will be instantiated and called as such:
# codec = Codec()
# codec.deserialize(codec.serialize(root))

个人实现

法一:DFS (preorder) 递归实现编码 + 哈希表 (中间形式) + DFS (preorder) 递归实现解码

经过第一次失败测试,我明白应该是序列化后的 中间形式 - 列表字符串 出了问题,因为列表需要对整棵树完全纪实 —— 即便是无效的空节点也要以 None 的形式记录。因此,对于小型树而言,算法尚能 work;对于大型树、特别是存在大量无效空节点的数 (最糟糕的情况就是测试用例的 超长单支树) 时,会造成时间和空间复杂度的极大冗余。为此,需要修改中间形式。

后来,我沿袭上一题的思路,使用 哈希表/字典 (dict) 取代列表 (list) 作为中间形式,只记录树中有效节点 (无效空节点则不计)。记录形式是 {index:val},其中,当前节点 node 的索引设为 index (准确地说是 数组 array 实现树的索引 index, 从 0 开始),其 左子节点 的索引设为 index*2+1右子节点 的索引设为 index*2+2。最后,使用递归实现序列化和反序列化算法的主体。从而,极大地降低了资源消耗。空间复杂度 O(n),时间复杂度 O(n)。

2020/07/29 - 45.93% (180ms) - 第一次自行解决困难级别 敲开森 ~ (该实现尚未在官网各题解中发现)

## 该实现尚未在官网各题解中发现 !
class Codec:
    def serialize(self, root):
        def encode_recur(node, index, record):
            if not node:
                return 
            record[index] = node.val                      # 当前节点索引为 index
            encode_recur(node.left, index*2+1, record)    # 当前节点左子节点索引为 index*2+1
            encode_recur(node.right, index*2+2, record)   # 当前节点右子节点索引为 index*2+2
            
        hashmap = {}                    # 仅记录有效节点的哈希表, 格式 {index:val}
        encode_recur(root, 0, hashmap)  # 有效节点哈希表记录
        return str(hashmap)             # 中间形式 - 哈希表字符串


    def deserialize(self, data):   
        def decode_recur(index, record):
            if index not in dic:                          # 索引不在哈希表中, 即为无效节点
                return None
            root = TreeNode(dic[index])                   # 根据索引取得节点值, 创建当前节点
            root.left = decode_recur(index*2+1, record)   # 当前节点指向左子节点 index*2+1
            root.right = decode_recur(index*2+2, record)  # 当前节点指向右子节点 index*2+2
            return root
        
        dic = eval(data)                # 去序列化还原有效节点哈希表 (str → dict)
        return decode_recur(0, dic)     # 返回根节点

官方实现与说明

// 这太暴力, 换其他语言在极端情况下要超时的 (就像我的失败测试)
// JAVA implementation
public class Codec {
    public String rserialize(TreeNode root, String str) {
        if (root == null) {
            str += "None,";
        } else {
            str += str.valueOf(root.val) + ",";
            str = rserialize(root.left, str);
            str = rserialize(root.right, str);
        }
        return str;
    }
  
    public String serialize(TreeNode root) {
        return rserialize(root, "");
    }
  
    public TreeNode rdeserialize(List<String> l) {
        if (l.get(0).equals("None")) {
            l.remove(0);
            return null;
        }
  
        TreeNode root = new TreeNode(Integer.valueOf(l.get(0)));
        l.remove(0);
        root.left = rdeserialize(l);
        root.right = rdeserialize(l);
    
        return root;
    }
  
    public TreeNode deserialize(String data) {
        String[] data_array = data.split(",");
        List<String> data_list = new LinkedList<String>(Arrays.asList(data_array));
        return rdeserialize(data_list);
    }
}


// C++ implementation
class Codec {
public:
    string serialize(TreeNode* root) {
        if (!root) return "X";
        auto l = "(" + serialize(root->left) + ")";
        auto r = "(" + serialize(root->right) + ")";
        return  l + to_string(root->val) + r;
    }

    inline TreeNode* parseSubtree(const string &data, int &ptr) {
        ++ptr; // 跳过左括号
        auto subtree = parse(data, ptr);
        ++ptr; // 跳过右括号
        return subtree;
    }

    inline int parseInt(const string &data, int &ptr) {
        int x = 0, sgn = 1;
        if (!isdigit(data[ptr])) {
            sgn = -1;
            ++ptr;
        }
        while (isdigit(data[ptr])) {
            x = x * 10 + data[ptr++] - '0';
        }
        return x * sgn;
    }

    TreeNode* parse(const string &data, int &ptr) {
        if (data[ptr] == 'X') {
            ++ptr;
            return nullptr;
        }
        auto cur = new TreeNode(0);
        cur->left = parseSubtree(data, ptr);
        cur->val = parseInt(data, ptr);
        cur->right = parseSubtree(data, ptr);
        return cur;
    }

    TreeNode* deserialize(string data) {
        int ptr = 0;
        return parse(data, ptr);
    }
};


反例说明

该反例利用先序遍历实现序列化和反序列化,中间形式为列表。然而,使用 + 拼接列表 list.pop(0) (复杂度 O(n)) 等都是十分低效的操作,而且作为中间形式的列表还记录了大量本可不比记录的无效空节点,显然不如个人实现法一

可见,单纯的代码简短并没有什么意义,有时宁愿多些几行也要降低复杂度或者提升可读性。此外,很多细节层面还是要注意,特别是 各种数据类型内置方法的复杂度 要有深入了解。

2020/07/29 - 13.24% (236ms)

class Codec:
    def serialize(self, root):
        def dfs(root):
            if not root: return [None]
            left_t = dfs(root.left)
            right_t = dfs(root.right)
            return [root.val] + left_t + right_t  # 低效!
        return str(dfs(root))
  
    def deserialize(self, data):
        data_list = eval(data)  # 解析出字符串中的list
        def dfs(data_l):
            root_val = data_list.pop(0)  # 低效!
            if root_val == None:  # 因为data_l中含有0元素所以这里不能用if not root_val
                return None
            root = TreeNode(root_val)
            root.left = dfs(data_l)
            root.right = dfs(data_l)
            return root
        return dfs(data_list)

 参考文献

https://leetcode-cn.com/explore/learn/card/data-structure-binary-tree/4/conclusion/20/

https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/solution/er-cha-shu-de-xu-lie-hua-yu-fan-xu-lie-hua-by-le-2/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值