[Notes] Trees - Binary Tree

Posted by 西维蜀黍的 OJ Blog on 2023-09-10, Last Modified on 2025-03-25

Traverse Binary Trees

Preorder(前序遍历)

Recursion

def preorder(root):
    if not root:
        return
    print(root.val)
    preorder(root.left)
    preorder(root.right)

Iterative (Without recursion)

  • use stack
	def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
		res = []
		stack = []
		cur = root
		while cur or stack:
			if cur:
				res.append(cur.val)
				stack.append(cur.right)
				cur = cur.left
			else:
				cur = stack.pop()
		return res

Inorder(中序遍历)

Recursion

	def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
		res = []

		def inorder(root):
			if not root:
				return

			inorder(root.left)
			res.append(root.val)
			inorder(root.right)
			return

		inorder(root)
		return res

Iterative (Without recursion)

  • use stack
class Solution:
	def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        stack = []
        cur = root  # cur 指向当前要被扫描的节点
        while cur or stack:  # 只有当前指向的节点为 None 且之前放进 stack 的节点都被处理
            while cur:  # 一直向左下找节点,直到其左下无节点了
                stack.append(cur)  # 之后要处理当前的中间节点,所以需要把中间节点存入 stack
                cur = cur.left
            cur = stack.pop(-1)  # 当前最左下元素为空后,从 stack 中找到中序节点,处理它。之后再处理其右子树

            k -= 1
            if k == 0:
                return cur.val
            cur = cur.right

ref

Preorder(前序遍历)

  • use queue
    def maxDepth(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: int
        """
        if not root:
            return 0

        depth = 0
        queue = [root]
        while queue:
            for _ in range(len(queue)):  # traverse all nodes of the current level
                node = queue.pop(0)
                # add its children only if they are not None
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
            depth += 1  # the node added to the queue is guaranteed not being None, and thus we could always +1 level
        return depth

Knowledge

题目

Basic Traversal

Traversal

解析

NICE binary tree

Question description
A NICE binary tree has the following properties:​
- root.val = 0​
- If treeNode.val is x and treeNode.left != null, then treeNode.left.val = 2 * x + 1​
- If treeNode.val is x and treeNode.right != null, then treeNode.right.val = 2 * x + 2​

Now the NICE tree is reseted, which means all treeNode.val have been changed to -1.​

Implement the TreeRecover class:​
- TreeRecover(TreeNode* root) Initializes the object with a reseted binary tree and recovers it into a NICE binary tree.​
- bool query(int target) Returns true if the target value exists in the NICE binary tree
  • The key insight is that for a given node value x, its left child is 2 * x + 1 and its right child is 2 * x + 2. This property allows us to determine the path to follow (left or right) for a specific target value without needing to traverse the entire tree.

class TreeNode:
	def __init__(self, v):
		self.val = v
		self.left = None
		self.right = None


class Solution:
	def check(self, root, target):
		found = [False]

		def dfs(root, target):
			if not root:
				return
			if root.val == target:
				found[0] = True
				return
      
			t = target
			while True:
				if t < root.val:
					return
				if (root.left and t == root.left.val) or (root.right and t == root.right.val):
					if root.left and t == root.left.val:
						dfs(root.left, target)
						return
					if root.right and t == root.right.val:
						dfs(root.right, target)
						return
				reminder = t % 2
				if reminder == 0:
					t = t / 2 - 1
				else:
					t = (t - 1) / 2

		dfs(root, target)
		return found[0]

	def construct(self):
		root = TreeNode(0)
		n1 = TreeNode(1)
		n2 = TreeNode(2)
		root.left = n1
		root.right = n2

		n3 = TreeNode(3)
		n4 = TreeNode(4)
		n1.left = n3
		n2.right = n4

		n5 = TreeNode(5)
		n6 = TreeNode(6)
		n2.left = n5
		n2.right = n6

		n7 = TreeNode(7)
		n8 = TreeNode(8)
		n3.left = n7
		n3.right = n8

		n9 = TreeNode(9)
		n10 = TreeNode(10)
		n4.left = n9
		n4.right = n10

		n11 = TreeNode(11)
		n12 = TreeNode(12)
		n5.left = n11
		n5.right = n12

		n13 = TreeNode(13)
		n6.left = n13
		return root


if __name__ == '__main__':
	s = Solution()
	root = s.construct()
	print(s.check(root, 13))

Traversal

前序遍历 - DFS/BFS - 100. Same Tree

  • 先看当前节点是否相同(是否不为 None,且值相同)
  • 再看当前节点的左子树 和 右子树 是否相同
# DPS - recursive
#   - time: O(n)
#   - space: O(h) # h is the height of the tree.
#       - best case (balanced tree): O(logn)
#       - worst case (degenerate tree): O(n)      
    def isSameTree(self, p, q):
        """
        :type p: Optional[TreeNode]
        :type q: Optional[TreeNode]
        :rtype: bool
        """
        if not p and not q:
            return True
        if p and q and p.val == q.val:
            return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
        else:
            return False


# DFS - Iterative (use Stack) via preorder
#   - time: O(n)
#   - space: O(n) 
    def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
        stack = [(p, q)]
        
        while stack:
            node1, node2 = stack.pop()
            
            if not node1 and not node2:
                continue
            if not node1 or not node2 or node1.val != node2.val:
                return False
                
            stack.append((node1.right, node2.right))
            stack.append((node1.left, node2.left))
        
        return True
      
# BFS - Iterative (use Stack) via preorder
#   - time: O(n)
#   - space: O(n)       
    def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
        q1 = deque([p])
        q2 = deque([q])

        while q1 and q2:
            for _ in range(len(q1)):
                nodeP = q1.popleft()
                nodeQ = q2.popleft()

                if nodeP is None and nodeQ is None:
                    continue
                if nodeP is None or nodeQ is None or nodeP.val != nodeQ.val:
                    return False

                q1.append(nodeP.left)
                q1.append(nodeP.right)
                q2.append(nodeQ.left)
                q2.append(nodeQ.right)

        return True

ref

前序遍历 - 572. Subtree of Another Tree

# DFS
#   - time: O(s*t)
#   - space:O(s*t)  
    def isSubtree(self, root, subRoot):
        """
        :type root: Optional[TreeNode]
        :type subRoot: Optional[TreeNode]
        :rtype: bool
        """

        def sameTree(p, q):  # define a helper function
            if not p and not q:
                return True
            if p and q and p.val == q.val:
                return (sameTree(p.left, q.left) and
                        sameTree(p.right, q.right))
            else:
                return False

        if not subRoot:  # None 是任何树的子树
            return True
        if not root:  # None 中不存在任何子树 是 任何非空树的子树
            return False

        if sameTree(root, subRoot):
            return True
        return (self.isSubtree(root.left, subRoot) or
                self.isSubtree(root.right,
                               subRoot))
        
# ??
# Serialization And Pattern Matching
#   - time: O(s+t)
#   - space: O(s+t)   
    def serialize(self, root: Optional[TreeNode]) -> str:
            if root == None:
                return "$#"
            
            return ("$" + str(root.val) + 
                    self.serialize(root.left) + self.serialize(root.right))  

    def z_function(self, s: str) -> list:
        z = [0] * len(s)
        l, r, n = 0, 0, len(s)
        for i in range(1, n):
            if i <= r:
                z[i] = min(r - i + 1, z[i - l])
            while i + z[i] < n and s[z[i]] == s[i + z[i]]:
                z[i] += 1
            if i + z[i] - 1 > r:
                l, r = i, i + z[i] - 1
        return z

    def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
        serialized_root = self.serialize(root)
        serialized_subRoot = self.serialize(subRoot)
        combined = serialized_subRoot + "|" + serialized_root
        
        z_values = self.z_function(combined)
        sub_len = len(serialized_subRoot)
        
        for i in range(sub_len + 1, len(combined)):
            if z_values[i] == sub_len:
                return True
        return False

ref

todo - 114. Flatten Binary Tree to Linked List

todo - 116. Populating Next Right Pointers in Each Node

前序遍历 - DFS/BFS - 226. Invert Binary Tree

  • 前序遍历:先对换节点,再去处理子节点们(对子节点们进行递归处理)
# DFS - recursion 
#  - time: O(n)
#  - space: O(1)
class Solution(object):
    def invertTree(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: Optional[TreeNode]
        """
        if not root:
            return None

        root.left, root.right = root.right, root.left
        self.invertTree(root.left)
        self.invertTree(root.right)
        return root
      
# DFS - interative   
#  - use stack to achieve
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if not root:
            return None
        stack = [root]
        while stack:
            node = stack.pop()
            node.left, node.right = node.right, node.left
            if node.left:
                stack.append(node.left)
            if node.right:
                stack.append(node.right)
        return root
      
# BFS
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if not root:
            return None
        queue = deque([root])
        while queue:
            node = queue.popleft()
            node.left, node.right = node.right, node.left
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        return root

ref

前序遍历 - DFS/BFS - 1448. Count Good Nodes in Binary Tree

  • 前序遍历:先处理当前节点,再处理其左右子树
# DFS
#  - 递归时,把 maxVal 传递下去,该值记录了,从 root 到当前节点经过的 path 上的所有节点的最大值。
#     - 如果当前节点的值 >= 该值,该节点是一个 good node
#  - root 永远是 good node
#  - time: O(n)
#  - space: O(n)
    def goodNodes(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """
        res = [0]

        def dfs(node, maxVal):
            if not node:
                return

            # 递归时,把 maxVal 传递下去,该值记录了,从 root 到当前节点经过的 path 上的所有节点的最大值。
            # 如果当前节点的值 >= 该值,该节点是一个 good node
            if node.val >= maxVal:
                res[0] += 1
            maxVal = max(maxVal, node.val)  # 更新当前看到过的最大值
            dfs(node.left, maxVal)
            dfs(node.right, maxVal)

        dfs(root, root.val)
        return res[0]
      
# BFS
#  - time: O(n)
#  - space: O(n)
	def goodNodes(self, root: TreeNode) -> int:
		res = [0]

		def bfs(root):
			if not root:
				return

			queue = [(root, -10000000)]  # (node, max_val_in_path)
			while queue:
				for i in range(len(queue)):
					n, max_val_in_path = queue.pop(0)
					if n.val >= max_val_in_path:
						res[0] += 1

					if n.left:
						queue.append((n.left, max(max_val_in_path, n.val)))
					if n.right:
						queue.append((n.right, max(max_val_in_path, n.val)))

		bfs(root)
		return res[0]

ref

BFS

前序遍历 - 102. Binary Tree Level Order Traversal

  • for i in range(len(queue)) 来记下当前这一层,一共有多少个不为空的节点,当遍历完他们后,就可以 res.append(level),因为这一层已经遍历完了
# BFS
#  - time: O(n)
#  - space: O(n)
    def levelOrder(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: List[List[int]]
        """
        if not root:
            return []

        res = []
        queue = [root]
        while queue:
            cur_level = []
            for _ in range(len(queue)):  # 当前queue中有多少个元素,则for loop多少次
                node = queue.pop(0)
                cur_level.append(node.val)
                if node.left:  # 保证 node 不为空时,才插入queue
                    queue.append(node.left)
                if node.right:  # 保证 node 不为空时,才插入queue
                    queue.append(node.right)
            res.append(cur_level)

Ref

前序遍历 - 199. Binary Tree Right Side View

# BFS(更 comes in nature)
#  - time: O(n)
#  - space: O(n)
    def rightSideView(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: List[int]
        """
        if not root:
            return []

        res = []
        queue = [root]
        while queue:
            levelTotal = len(queue)  # 记录下当时 queue 里有的元素的个数(当前 level 有的元素的个数)
            for i in range(levelTotal):
                n = queue.pop(0)
                if n.left:
                    queue.append(n.left)
                if n.right:
                    queue.append(n.right)

                if i == levelTotal - 1:  # 如果当前元素为当前 level 元素的最后一个
                    res.append(n.val)

        return res
# DFS
#  - time: O(n)
#  - space: O(n)      
class Solution:
    def rightSideView(self, root: Optional[TreeNode]) -> List[int]:
        res = []

        def dfs(node, depth):
            if not node:
                return None
            if depth == len(res):
                res.append(node.val)
            
            dfs(node.right, depth + 1)
            dfs(node.left, depth + 1)
        
        dfs(root, 0)
        return res      

ref

Depth

前序遍历 - DFS/BFS - 104. Maximum Depth of Binary Tree

  • 前序遍历

  • 类似的问题:如果把根节点看做第 1 层,如何打印出每一个节点所在的层数?

    • 	def printCurDepth(self, root: Optional[TreeNode]) -> int:
      		def dfs(root, parent_depth):
      			if not root:
      				return 0
      
      			cur_depth = parent_depth + 1
      			print(cur_depth)
      			dfs(root.left, cur_depth)
      			dfs(root.right, cur_depth)
      
      		dfs(root, 0)
      
# DPS - recursive 
#   - time: O(n)
#   - space: O(h) # h is the height of the tree.
#       - best case (balanced tree): O(logn)
#       - worst case (degenerate tree): O(n)
class Solution(object):
    def maxDepth(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: int
        """
        if not root: # 如果该节点为空,则该子树的depth为0
            return 0

        return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right))  # 因为此时 root不为 None,因而其本身使得 height + 1

# DFS - Iterative (use Stack) via preorder
#   - time: O(n)
#   - space: O(n) 
class Solution:
    def maxDepth(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: int
        """
        if not root:
            return 0

        max_depth = 1
        stack = [(root, 1)]  # (node, depth)
        while stack:
            node, depth = stack.pop(0)
            max_depth = max(max_depth, depth)
            if node.left:
                stack.append((node.left, depth + 1))
            if node.right:
                stack.append((node.right, depth + 1))
        return max_depth

# BFS (use queue)
#   - time: O(n)
#   - space: O(n) 
class Solution:
    def maxDepth(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: int
        """
        if not root:
            return 0

        depth = 0
        queue = [root]
        while queue:
            for _ in range(len(queue)):  # traverse all nodes of the current level
                node = queue.pop(0)
                # add its children only if they are not None
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
            depth += 1  # the node added to the queue is guaranteed not being None, and thus we could always +1 level
        return depth

ref

后序遍历 - DFS - 110. Balanced Binary Tree

  • 判断是否 balanced: abs(left_height - right_height) <= 1
# DPS - recursive 
#   - time: O(n)
#   - space: O(h) # Count the space used by the call-stack. If not, it is O(1). h is the height of the tree.
#       - best case (balanced tree): O(logn)
#       - worst case (degenerate tree): O(n)
    def isBalanced(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: bool
        """
        res = [True]

        def dfs(node):
            if not res[0]:  # If found not-balance, no need to cal further
                return 0
            if not node:
                return 0

            left_height = dfs(node.left)
            right_height = dfs(node.right)

            # compare the diff between left_height and right_height to know the balance
            if abs(left_height - right_height) > 1:
                res[0] = False
            return max(left_height, right_height) + 1  # 因为此时 node 不为空,找到一个节点时,height就可以加1

        dfs(root)
        return res[0]

# DFS - Iterative (use Stack) via preorder
#   - time: O(n)
#   - space: O(n) 
    def isBalanced(self, root):
        stack = []
        node = root
        last = None
        depths = {}

        while stack or node:
            if node:
                stack.append(node)
                node = node.left
            else:
                node = stack[-1]
                if not node.right or last == node.right:
                    stack.pop()
                    left = depths.get(node.left, 0)
                    right = depths.get(node.right, 0)

                    if abs(left - right) > 1:
                        return False

                    depths[node] = 1 + max(left, right)
                    last = node
                    node = None
                else:
                    node = node.right

        return True

ref

后序遍历 - DFS - 543. Diameter of Binary Tree

  • 更像一个 medium 问题

  • 后序遍历

  • 思路

    • Diameter = 左子树的 depth + 右子树的 depth

    • 用一个全部变量记下当前看过的最大的 diameter

    • dfs 函数返回以当前节点作为根节点的数的最大 depth

    • 如果一个节点没有左子树,则左侧的 depth 为 0

  • 类似的问题:打印出每个节点的左右子树各有多少节点?

    • # DFS
      def printSubTreeNodeNum(self, root: Optional[TreeNode]) -> int:
      		if not root:
      			return 0
      
      		left_num = self.printSubTreeNum(root.left)
      		right_num = self.printSubTreeNum(root.right)
      		print("left_sub_tree_node_num:", left_num)
      		print("right_sub_tree_node_num:", right_num)
      		return left_num + right_num + 1
      
# DFS - recursion
#   - space: O(h) # h is the height of the tree.
#       - best case (balanced tree): O(logn)
#       - worst case (degenerate tree): O(n)
    def diameterOfBinaryTree(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: int
        """
        res = [0]  # 用 [0] 而不是 0 来避免报错

        def dfs(node):
            if not node:
                return 0

            left_height = dfs(node.left)
            right_height = dfs(node.right)

            # 计算出当前节点的 diameter,更新到 res 中
            cur_diameter = left_height + right_height
            res[0] = max(cur_diameter, res[0])
            return max(left_height, right_height) + 1  # 返回当前节点的最高高度(考虑了左子树和右子树)

        dfs(root)
        return res[0]
  
# DFS - Iterative  
#  - time: O(n)
#  - space: O(n)
    def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
        stack = [root]
        mp = {None: (0, 0)}

        while stack:
            node = stack[-1]

            if node.left and node.left not in mp:
                stack.append(node.left)
            elif node.right and node.right not in mp:
                stack.append(node.right)
            else:
                node = stack.pop()

                leftHeight, leftDiameter = mp[node.left]
                rightHeight, rightDiameter = mp[node.right]

                mp[node] = (1 + max(leftHeight, rightHeight),
                           max(leftHeight + rightHeight, leftDiameter, rightDiameter))

        return mp[root][1]

Ref

Construct Binary Tree by Traversal

105. Construct Binary Tree from Preorder and Inorder Traversal

  • 如果 len (preorder) == 0,直接不需要构造 Tree
  • 如果 len (preorder) > 0,root 节点一定是 preorder [0],因为先序遍历的规律就是先访问 root 节点
    • 然后在中序遍历的结果中,找到这个 root 节点的索引位置,在中序遍历中,该 root 节点左边的那些节点,就是该 root 节点的左子树的节点,因为中序遍历的规律就是访问完左子树后,才访问 root 节点
    • 又因为中序遍历的规律就是访问完 root 节点后,才访问右子树,所以 root 节点后的那些节点,就是该 root 节点的右子树的节点
    • 因为无论是前序遍历还是中序遍历,都是最后访问右子树,所以对于中序遍历和前序遍历,访问完 root 节点后要访问的节点都是属于右子树的节点
  • 递归
preorder: [3   9   20   15   7]
inorder:  [9   3   15   20   7]
-> 说明 root 节点是 3
-> 去 inorder 中,找到 root 节点的索引,即 1
-> 所以 inorder[0:(1)] 是左子树的 inorder
-> 左子树的 preorder 是 preorder[(1): (1) + 1]
-> 右子树的 inorder  是 inorder[(1) + 1:]
-> 右子树的 preorder 是 preorder[(1) + 1:]
# DFS
#  - time: O(n^2) # 递归 O(n) + find_by_index O(n)
#  - space: O(n)
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
		if len(preorder) == 0:
			return None

		v = preorder[0]
		parent = TreeNode(v)
		inorder_spliter_idx = inorder.index(v)

		left_preorder = preorder[1:inorder_spliter_idx + 1]
		left_inorder = inorder[:inorder_spliter_idx]
		parent.left = self.buildTree(left_preorder, left_inorder)

		right_preorder = preorder[inorder_spliter_idx + 1:]
		right_inorder = inorder[inorder_spliter_idx + 1:]
		parent.right = self.buildTree(right_preorder, right_inorder)
		return parent
  
# DFS + hashmap
#  - time: O(n) # 递归 O(n) + find_by_index O(1)
#  - space: O(n)  # 除去返回的答案需要的 O(n) 空间之外,我们还需要使用 O(n) 的空间存储哈希映射,以及 O(h)(其中 h 是树的高度)的空间表示递归时栈空间。这里 h<n,所以总空间复杂度为 O(n)。
    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        indices = {val: idx for idx, val in enumerate(inorder)}
        
        self.pre_idx = 0
        def dfs(l, r):
            if l > r:
                return None

            root_val = preorder[self.pre_idx]
            self.pre_idx += 1
            root = TreeNode(root_val)
            mid = indices[root_val]
            root.left = dfs(l, mid - 1)
            root.right = dfs(mid + 1, r)
            return root

        return dfs(0, len(inorder) - 1)

# DFS (optimal)      
#  - time: O(n) # 递归 O(n)
#  - space: O(n)  # for recursion stack
    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        preIdx = inIdx = 0
        def dfs(limit):
            nonlocal preIdx, inIdx
            if preIdx >= len(preorder):
                return None
            if inorder[inIdx] == limit:
                inIdx += 1
                return None
            
            root = TreeNode(preorder[preIdx])
            preIdx += 1
            root.left = dfs(root.val)
            root.right = dfs(limit)
            return root
        return dfs(float('inf'))      

ref

106. Construct Binary Tree from Inorder and Postorder Traversal

# DFS
#  - time: O(n^2) # 递归 O(n) + find_by_index O(n)
#  - space: O(n)
def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
        if not postorder or not inorder:
            return None

        root = TreeNode(postorder[-1])
        mid = inorder.index(postorder[-1])
        root.left = self.buildTree(inorder[:mid], postorder[:mid])
        root.right = self.buildTree(inorder[mid + 1:], postorder[mid:-1])
        return root

# DFS + hashmap
#  - time: O(n) # 递归 O(n) + find_by_index O(1)
#  - space: O(n)  # 除去返回的答案需要的 O(n) 空间之外,我们还需要使用 O(n) 的空间存储哈希映射,以及 O(h)(其中 h 是树的高度)的空间表示递归时栈空间。这里 h<n,所以总空间复杂度为 O(n)。      
    def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
        inorderIdx = {v: i for i, v in enumerate(inorder)}

        def dfs(l, r):
            if l > r:
                return None

            root = TreeNode(postorder.pop())
            idx = inorderIdx[root.val]
            root.right = dfs(idx + 1, r)
            root.left = dfs(l, idx - 1)
            return root

        return dfs(0, len(inorder) - 1)    
      
# DFS (optimal)      
#  - time: O(n) # 递归 O(n)
#  - space: O(n)  # for recursion stack      
    def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
        postIdx = inIdx = len(postorder) - 1
        def dfs(limit):
            nonlocal postIdx, inIdx
            if postIdx < 0:
                return None
            if inorder[inIdx] == limit:
                inIdx -= 1
                return None
            
            root = TreeNode(postorder[postIdx])
            postIdx -= 1
            root.right = dfs(root.val)
            root.left = dfs(limit)
            return root
        return dfs(float('inf'))

ref

889. Construct Binary Tree from Preorder and Postorder Traversal

	def constructFromPrePost(self, preorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
		if not preorder:
			return None

		root_val = preorder[0]
		root = TreeNode(root_val)

		preorder_splitter_idx = 0
		preorder_set = set()
		postorder_set = set()
		for idx in range(1, len(preorder)):
			preorder_set.add(preorder[idx])
			postorder_set.add(postorder[idx - 1])
			if preorder_set == postorder_set:
				preorder_splitter_idx = idx
				break

		root.left = self.constructFromPrePost(preorder[1:preorder_splitter_idx + 1], postorder[0:preorder_splitter_idx])
		root.right = self.constructFromPrePost(preorder[preorder_splitter_idx + 1:],
											   postorder[preorder_splitter_idx:len(postorder) - 1])
		return root

序列化

比如说,如果给你一棵二叉树的前序遍历结果,你是否能够根据这个结果还原出这棵二叉树呢?

答案是也许可以,也许不可以,具体要看你给的前序遍历结果是否包含空指针的信息。如果包含了空指针,那么就可以唯一确定一棵二叉树,否则就不行。

举例来说,如果我给你这样一个不包含空指针的前序遍历结果 [1,2,3,4,5],那么如下两棵二叉树都是满足这个前序遍历结果的:

所以给定不包含空指针信息的前序遍历结果,是不能还原出唯一的一棵二叉树的。

但如果我的前序遍历结果包含空指针的信息,那么就能还原出唯一的一棵二叉树了。比如说用 # 表示空指针,上图左侧的二叉树的前序遍历结果就是 [1,2,3,#,#,4,#,#,5,#,#],上图右侧的二叉树的前序遍历结果就是 [1,2,#,3,#,#,4,5,#,#,#],它俩就区分开了。

那么估计就有聪明的小伙伴说了:东哥我懂了,甭管是前中后序哪一种遍历顺序,只要序列化的结果中包含了空指针的信息,就能还原出唯一的一棵二叉树了。

首先要夸一下这种举一反三的思维,但很不幸,正确答案是,即便你包含了空指针的信息,也只有前序和后序的遍历结果才能唯一还原二叉树,中序遍历结果做不到。

本文后面会具体探讨这个问题,这里只简单说下原因:因为前序 / 后序遍历的结果中,可以确定根节点的位置,而中序遍历的结果中,根节点的位置是无法确定的。

更直观的,比如如下两棵二叉树显然拥有不同的结构,但它俩的中序遍历结果都是 [#,1,#,1,#],无法区分:

说了这么多,总结下结论,当二叉树中节点的值不存在重复时

  1. 如果你的序列化结果中不包含空指针的信息,且你只给出一种遍历顺序,那么你无法还原出唯一的一棵二叉树。

  2. 如果你的序列化结果中不包含空指针的信息,且你会给出两种遍历顺序,那么按照前文 东哥手把手带你刷二叉树(构造篇) 所说,分两种情况:

    1. 如果你给出的是前序和中序,或者后序和中序,那么你可以还原出唯一的一棵二叉树。
    2. 如果你给出前序和后序,那么你无法还原出唯一的一棵二叉树。
  3. 如果你的序列化结果中包含空指针的信息,且你只给出一种遍历顺序,也要分两种情况:

    1. 如果你给出的是前序或者后序,那么你可以还原出唯一的一棵二叉树。
    2. 如果你给出的是中序,那么你无法还原出唯一的一棵二叉树。

Ref

todo - 297. Serialize and Deserialize Binary Tree

class Codec:
    def serialize(self, root):
        res = []

        def dfs(node):
            if not node:
                res.append("N")
                return
            res.append(str(node.val))
            dfs(node.left)
            dfs(node.right)

        dfs(root)
        return ",".join(res)

    def deserialize(self, data):
        vals = data.split(",")
        self.i = 0

        def dfs():
            if vals[self.i] == "N":
                self.i += 1
                return None
            node = TreeNode(int(vals[self.i]))
            self.i += 1
            node.left = dfs()
            node.right = dfs()
            return node

        return dfs()

ref

todo - 652. Find Duplicate Subtrees

  • 用 DFS 来序列化一个 tree,且配合一个 hashmap 来判断是否 duplicate
	def findDuplicateSubtrees(self, root: Optional[TreeNode]) -> List[Optional[TreeNode]]:
		key_to_nodes_map = {}
		res = []  # space: O(n2)

		def dfs(root):
			if not root:
				return "null"

			k = ",".join([str(root.val), dfs(root.left), dfs(root.right)])
			if k not in key_to_nodes_map:
				key_to_nodes_map[k] = [root]
			else:
				if len(key_to_nodes_map[k]) == 1:
					res.append(root)

				key_to_nodes_map[k].append(root)
			return k

		dfs(root)  # time: O(n)
		return res

ref

Misc

todo - 116. Populating Next Right Pointers in Each Node

# BFS with stack
# time: O(n)
# address: O(n)
def connect(self, root: 'Optional[Node]') -> 'Optional[Node]':
		if not root:
			return None

		queue = [root]
		while queue:
			t = None
			for i in range(len(queue)):
				node = queue.pop(0)
				if node.left and node.right:
					queue.append(node.left)
					queue.append(node.right)

				if t:
					t.next = node
				t = node
		return root
  
# BFS without stack
# time: O(n)
# address: O(1)
	def connect(self, root: 'Optional[Node]') -> 'Optional[Node]':
		if not root:
			return None

		cur = root
		left = root.left
		while cur and left:  # go bottom
			next_level_cur = cur.left
			while cur:  # go right
				left.next = cur.right
				if cur.next:
					cur.right.next = cur.next.left
				cur = cur.next
				if cur:
					left = cur.left

			cur = next_level_cur
			left = next_level_cur.left
		return root

ref

todo - 654. Maximum Binary Tree