目录
一、从中序与后序遍历序列构造二叉树
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/
二、从前序与中序遍历序列构造二叉树
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/
三、填充每个节点的下一个右侧节点指针
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/
四、填充每个节点的下一个右侧节点指针 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/
五、二叉树的最近公共祖先
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/
六、二叉树的序列化与反序列化
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/