leetcode - 二叉树

前序/中序/后序/层序遍历(非递归版本)

递归版本见注释部分代码

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

迭代方法使用栈或者队列。当前访问的节点一定是栈顶的节点(都是先将根节点入栈),先取出,再按照遍历顺序将其他节点入栈。

①前序遍历

这个方法是先读取根节点结果,右子树、左子树分别入栈,出栈输出结果则是根->左->右

class Solution:
    # def __init__(self):
    #     self.res = []
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        # 递归
        #  if root: 
        #     self.res.append(root.val)
        #     self.preorderTraversal(root.left)
        #     self.preorderTraversal(root.right)
        # return self.res
            
        # 栈
        res = []
        if not root: return res
        s = [root]
        while s:
            node = s.pop()
            res.append(node.val)
            if node.right: s.append(node.right)
            if node.left: s.append(node.left)            
        return res

② 中序遍历
只对根节点做出入栈的处理。根先入栈,遍历左子树,根出栈,遍历右子树,则输出结果为左->根->右。

class Solution:
    # def __init__(self):
    #     self.res = []
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        # 递归
        # if root:
        #     self.inorderTraversal(root.left)
        #     self.res.append(root.val)
        #     self.inorderTraversal(root.right)
        # return self.res
        
        # 栈
        res = [] 
        if not root :return res
        s = []
        while s or root:
            if root:
                s.append(root)
                root = root.left
            else:
                root = s.pop()
                res.append(root.val)
                root = root.right
        return res

③后序遍历

巧妙借用前序的思想,先读取根节点结果,左子树、右子树依次入栈,则输出为根->右->左,最后将输出逆序就能生成正确的后序输出:左->右->根。

class Solution:
    # def __init__(self):
    #     self.res = []
    def postorderTraversal(self, root):
        # 递归
        # if root:
        #     self.postorderTraversal(root.left)
        #     self.postorderTraversal(root.right)
        #     self.res.append(root.val)
        # return self.res
        
        # 栈
        res = []
        if not root: return res
        s = [root]
        while s:
            node = s.pop()
            res.append(node.val)
            if node.left: s.append(node.left)
            if node.right: s.append(node.right)
        return res[::-1]

还有一种更容易理解的方法:“颜色标记法”,兼具栈迭代方法的高效,又像递归方法一样简洁易懂,更重要的是,这种方法对于前序、中序、后序遍历,能够写出完全一致的代码。

其核心思想如下:

  • 使用颜色标记节点的状态,新节点为白色,已访问的节点为灰色。
  • 如果遇到的节点为白色,则将其标记为灰色,然后将其右子节点、自身、左子节点依次入栈。
  • 如果遇到的节点为灰色,则将节点的值输出
class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        WHITE, GRAY = 0, 1
        stack, res = [(WHITE, root)], []
        while stack:
            color, node = stack.pop()  
            if node is None:
                continue
            if color == WHITE:
                stack.append((GRAY, node)) #前序/中序/后序调整下入栈的顺序就可以。因为出栈顺序跟入栈的相反,而前中后都是先左后右,只是根的位置不同,所以右都比左先入栈。这行放最前就是后序,放中间就是中序,放最后就是前序。
                stack.append((WHITE, node.right))
                stack.append((WHITE,node.left))  
            else:
                res.append(node.val)
        return res 

④ 层序遍历

用队列的方法,每次访问队首节点,并将其左、右子树入队。

class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        q, res = [root], []
        while q:
            level = []
            tmp_q, q = q, []
            for node in tmp_q:
                if node:
                    level.append(node.val)
                    q.append(node.left)
                    q.append(node.right)
            if level:
                res.append(level)
        return res

2)递归方法中的自顶向下 / 自底向上

举例:求二叉树最大深度

① 自顶向下( 通过函数传值的方法,从顶层传到底层)

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        return self.dfs(root,0)
    def dfs(self, root, depth):
        if not root: return depth
        l_depth = self.dfs(root.left,depth+1)
        r_depth = self.dfs(root.right,depth+1)
        return max(l_depth,r_depth)

② 自底向上

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root: return 0
        l_depth = self.maxDepth(root.left)
        r_depth = self.maxDepth(root.right)
        return max(l_depth,r_depth)+1

练习:

1)对称二叉树
思路:

  • 它们的两个根结点具有相同的值;
  • 每个树的右子树都与另一个树的左子树镜像对称。
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        if not root:
            return True
        return self.dfs(root.left,root.right)
        
    def dfs(self,node1,node2):
        if node1 == None and node2 == None : #左、右都为叶子节点
            return True
        elif node1 == None :  #左、右仅一方为叶子节点
            return False
        elif node2 == None:
            return False
        else:
            return (node1.val == node2.val) \
                    and self.dfs(node1.left,node2.right) \
                    and self.dfs(node1.right,node2.left)
        

2)路径总和
给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。

思路:自顶向下,向叶子节点传值

class Solution:
    def hasPathSum(self, root: TreeNode, sum: int) -> bool:
        if not root:
            return False
        sum -= root.val
        if not root.left and not root.right: # 叶子节点计算结果
            return sum == 0  
        return self.hasPathSum(root.left,sum) \
               or self.hasPathSum(root.right,sum)  #自顶向下,向叶子节点传值

3)从中序与后序遍历序列构造二叉树
在这里插入图片描述
注:题目规定,二叉树中没有重复的元素。
思路:

  • 后序遍历最后一个为root节点
  • 根据root节点在中序遍历的位置i,可以确定:
    中序遍历中,inorder[:i]为左子树, inorder[i+1:]为右子树;
    后序遍历中,postorder[:i]为左子树,postorder[i:len(postorder)-1]为右子树。
class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
        if not inorder or not postorder: 
            return None
        root_val = postorder[-1] # 后序遍历最后一个为root
        root, root_inorder_idx = TreeNode(root_val), inorder.index(root_val)
        root.left = self.buildTree(inorder[:root_inorder_idx],postorder[:root_inorder_idx])
        root.right = self.buildTree(inorder[root_inorder_idx+1:],postorder[root_inorder_idx:len(postorder)-1])
        return root 

4)从前序与中序遍历序列构造二叉树
在这里插入图片描述
思路:
前序遍历第一个为root节点。
根据root节点在中序遍历的位置i,可以确定:
中序遍历中,inorder[:i]为左子树, inorder[i+1:]为右子树
前序遍历中,preorder[1:i+1]为左子树,preorder[i+1:]为右子树。

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        if not inorder or not preorder: 
            return None
        root_val = preorder[0] # 前序遍历第一个为root
        root, root_inorder_idx = TreeNode(root_val), inorder.index(root_val)
        root.left = self.buildTree(preorder[1:root_inorder_idx+1],inorder[:root_inorder_idx])
        root.right = self.buildTree(preorder[root_inorder_idx+1:],inorder[root_inorder_idx+1:])
        return root

5)填充每个节点的下一个右侧节点指针(满二叉树)
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。初始状态下,所有 next 指针都被设置为 NULL。
在这里插入图片描述
方法1:层次遍历,无论二叉树怎么变化都通用,空间复杂度O(n)。

class Solution:
    def connect(self, root: 'Node') -> 'Node':
        if not root: return None
        q, res = [root], []
        while q:
            level = []
            len_q = len(q)
            for i, node in enumerate(q):
                if i == len_q-1:
                    node.next = None
                else:
                    node.next = q[i+1]
            tmp_q, q = q, []  #更新下一层队列
            for node in tmp_q:
                if node.left:  #确保队列中是非空节点
                    q.append(node.left)
                if node.right:
                    q.append(node.right)
        return root

方法2:观察到满二叉树的next是固定的,即left.next是root.right,right.next是root.next.left。(root表示为父节点)

class Solution:
    def connect(self, root: 'Node') -> 'Node':
        if not root:
            return None
        if root.left:
            root.left.next = root.right
        if root.right and root.next:
            root.right.next = root.next.left 
        self.connect(root.left)
        self.connect(root.right)
        return root

6)填充每个节点的下一个右侧节点指针II(非满二叉树)
在这里插入图片描述
和满二叉树不同的是,next不是固定的:

  • left.next 可能是root.right,或者需要不断地从root.next按从左到右的顺序查找。
  • right.next同样,需要不断地从root.next按从左到右的顺序查找。
class Solution:
    def connect(self, root: 'Node') -> 'Node':
        if not root: return None
        if root.left:  
            if root.right:
                root.left.next = root.right
            else:
                root.left.next = self.look_for_father_next(root)
        if root.right:
            root.right.next = self.look_for_father_next(root)
        self.connect(root.right)  # 因为需要查找父节点的next,递归顺序先右再左
        self.connect(root.left)
        return root
    def look_for_father_next(self, root):
        if not root.next:
            return None
        while root.next:  #不断地从父节点的next节点按从左到右的顺序查找
            if root.next.left:
                return root.next.left
            if root.next.right:
                return root.next.right
            root = root.next
        return None

7) 二叉树的最近公共祖先
在这里插入图片描述
思路:
由于每个节点只有唯一一个父节点,我们可以使用到字典的value-key的形式(节点-父节点)字典中预置根节点的父节点为None。

字典建立完成后,二叉树就可以看成一个所有节点都将最终指向根节点的链表了。于是在二叉树中寻找两个节点的最小公共节点就相当于,在一个链表中寻找他们相遇的节点。(参考leetcode 160.相交链表)

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        father_dict = {root:None}
        def dfs(node):
            if node:
                if node.left:
                    father_dict[node.left] = node
                if node.right:
                    father_dict[node.right] = node
                dfs(node.left)
                dfs(node.right)
        dfs(root)
        l1, l2 = p, q
        while l1 != l2 :  #于是在二叉树中寻找两个节点的最小公共节点就相当于,在一个链表中寻找他们相遇的节点(假定最近公共祖先存在)
            l1 = father_dict.get(l1, q) 
            l2 = father_dict.get(l2, p)
        return l1

8)二叉树的序列化与反序列化
在这里插入图片描述
思路:
题目要求对层次遍历做序列化和反序列化,可以借助两个队列,一个保存父节点,一个保存孩子节点,一层层更新。

class Codec:
    def serialize(self, root):
        """Encodes a tree to a single string.
        
        :type root: TreeNode
        :rtype: str
        """
        q, res = [root], []
        if not root: 
            return res
        while q:
            tmp_q, q = q, []
            for node in tmp_q:
                if node:
                    res.append(str(node.val))
                    q.append(node.left)
                    q.append(node.right)
                else:
                    res.append('null')
        while res[-1] == 'null':  #删除末尾的null
            res.pop()
        return ','.join(res)    #题目要求返回str

    def deserialize(self, data):
        """Decodes your encoded data to tree.
        
        :type data: str
        :rtype: TreeNode
        """
        if type(data) == str:
            data = data.split(',')

        length = len(data)
        if not length:
            return None
        root = TreeNode(data[0])
        parents, childs = [root], []
        i = 1  # 孩子结点在data中的索引
        while i < length:
            for parent in parents:
                if i < length and data[i]:  #更新左子树
                    node = TreeNode(data[i])
                    parent.left = node
                    childs.append(node)
                i += 1
                if i < length and data[i]:  #更新右子树
                    node = TreeNode(data[i])
                    parent.right = node
                    childs.append(node)
                i += 1
            if childs:    # 更新下一层,将childs作为新的fathers
                parents = childs
                childs = []
        return root
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章