二叉树常见解题方法

762 阅读6分钟

本文主要简述在leecode中解二叉树类型题目使用的常见方法,每种解题方法通过leetcode例题进行分析。不妥之处,欢迎指正。

一、利用递归遍历解题

二叉树递归解题重点在于利用思考根节点、左子树、右子数的关系,其余工作交给遍历处理,二叉树基本框架:

    if root == Nonereturn None
    
    # 前序遍历:do_something
    traverseTree(root.left)
    # 中序遍历:do_something
     traverseTree(root.right)
    # 后序遍历:do_something

题目1:二叉树的深度(leetcode)

1.png

思路: 思考题干中树的深度和根节点、左子树、右子数的关系,不难得出关系:树的深度=max(左子树深度,右子树深度)+1,利用框架得出如下代码:

    def maxDepth(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """
        if not root:
            return 0
            
         # 求左子树深度
        ldepth = self.maxDepth(root.left)
         # 求右子树深度
        rdepth = self.maxDepth(root.right)
        
        # 树的深度
        depth = max(ldepth,rdepth) + 1
        
        return depth

题目2:二叉树的直径(leetcode)

2.png

思路: 任何直径路径必然存在一个节点,将路径分隔为左右子数(左右子树可为空)。对于以该节点为根节点的子树而言,树直径路径的长度可转化为树的深度,公式:树的直径=左子树深度+右子树深度。所以求出所有节点的左右子树深度,得出所有子树的直径,最大者为树的直径。如下图所示:

3.jpg

选中节点左子树深度为3,选中节点右子树深度也为3,因此该选中子树的直径为3+3=6。

    def diameterOfBinaryTree(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """
        self.ans = 0
        def depth(node):
            if not node:
                return 0
            L = depth(node.left)
            R = depth(node.right)
            self.ans = max(self.ans,L+R)
            return max(L,R)+1
        depth(root)
        return self.ans 

1、特殊场景-两个节点同时遍历

两个节点同时遍历主要针对需要同时处理两个节点的问题,重点在于思考两个节点之间需要做哪些操作,同时需要如何遍历后两个子节点。基本框架如下:

    if root == None:
        return True
    return self.helper(root.left,root.right)

    def helper(self,node1,node2):
        if 达到边界条件:
            return 
            
        # 对两个节点进行某些操作do_something()    
        self.helper(node1.子节点,node2.子节点) 

题目3:对称二叉树(leetcode)

4.png

思路: 两个节点同时遍历有时是解决某类特殊问题的重要法宝,如对称二叉树,需判断每一层的对称节点值是否一致,很明显需要同时遍历两个节点,因此在函数签名中,需要设置两个入参并同时递归,该方式可以将特定关系的两个节点全部遍历一次。

    def isSymmetric(self, root):
            """
            :type root: TreeNode
            :rtype: bool
            """
            if root == None:
                return True
            return self.helper(root.left,root.right)

        def helper(self,node1,node2):
            # 若两个节点非空且数值相同,则用递归判断两个节点的子节点是否相同
            if node1 != None and node2 != None and node1.val == node2.val:
                return self.helper(node1.left,node2.right) and self.helper(node1.right,node2.left)
            # 边界条件,若两个节点均为空,则返回True
            if node1 == None and node2 == None:
                return True

            return False

题目4:填充每个节点的下一右侧节点指针(leetcode)

5.png

6.png

该题可以用过递归遍历,也可以通过层次遍历解答。

思路: 本次分析递归遍历。根据观察可知,需要将同一层次的两个节点依次关联,因此可以考虑遍历的特殊场景-两个节点同时遍历。构建辅助函数connectTwoNodes,该函数接收两个节点入参,完成将两节点串联的功能,边界条件为第一个节点为空。通过递归调用可以将所有的节点按要求串联起来。

    def connect(self, root):
            """
            :type root: Node
            :rtype: Node
            """
            if root == None:
                return root

            root.next = None
            self.connectTwoNodes(root.left,root.right)
            return root

    # 连接两个节点
    def connectTwoNodes(self,firstNode,secondNode):
        # 子节点为空,直接返回
        if firstNode == None:
            return None
        firstNode.next = secondNode
        secondNode.next = None
        # 将第一个节点的左子节点和第一个节点的右子节点连接
        self.connectTwoNodes(firstNode.left,firstNode.right)
        # 将第一个节点的右子节点和第二个节点的左子节点连接
        self.connectTwoNodes(firstNode.right,secondNode.left)
        # 将第二个节点的左子节点和第二个节点的右子节点连接
        self.connectTwoNodes(secondNode.left,secondNode.right)
        return None

2、特殊场景-构建二叉树

二叉树的增删等行为会影响二叉树的节点左右指针,所以相比于遍历需要注意对二叉树节点左右指针的赋值。

题目5:从前序与中序遍历序列构造二叉树(leetcode)

7.png

思路: 根据遍历的性质可以得知:可通过前序遍历确定二叉树的根节点,二叉树的根节点进而将中序遍历划分左边部分构建左子树、右边部分构建右子树,最后通过递归将整棵二叉树构建完毕,注意构建过程中需要将根节点的左右指针进行赋值。

    def buildTree(self, preorder, inorder):
        """
        :type preorder: List[int]
        :type inorder: List[int]
        :rtype: TreeNode
        """

        if len(preorder) == 0:
            return None

        # 从前序遍历序列中取出树的根节点
        root = TreeNode(preorder[0])
        # 根据根节点在中序遍历中的位置划分左子树和右子树
        index = inorder.index(preorder[0])

        # 递归构建左子树,节点指针赋值
        root.left = self.buildTree(preorder[1:index+1],inorder[0:index])
        # 递归构建右子树,节点指针赋值
        root.right = self.buildTree(preorder[index+1:],inorder[index+1:])

        return root

二、利用宽度优先遍历解题

先看一下宽度优先搜索(BFS),如下图遍历方式:

8.jpg

BFS通常借队列数据结构实现,框架如下:

    if root == Nonereturn None
    # 使用队列,实现节点FIFO
    lst = []
    lst.append(root)  

    while lst != []:
        node =  lst.pop(0) 

        # 遍历,do_something
        if node.left != None:
            lst.append(node.left)
        if node.right != None:
            lst.append(node.right)  

但通常我们解题过程中需要知道每个节点所处的层次关系,BFS无法区分各节点层次关系。所以需要在BFS遍历过程中加入计数器,记录每个层次的节点数量,从而形成层次遍历。框架如下:

    if root == Nonereturn None
    # 使用队列,实现节点FIFO
    lst = []
    lst.append(root)  

    while lst != []:
        # 每次到达新行时便计算此行的节点数n
        n = len(lst)

        for i in range(n):
            node =  lst.pop(0) 
            # 遍历,do_something
            if node.left != None:
                lst.append(node.left)
            if node.right != None:
                lst.append(node.right) 

题目6:二叉树的层序遍历(leetcode)

9.png

思路: 直接上层次遍历框架即可

    def levelOrder(self, root):
        """
        :type root: TreeNode
        :rtype: List[List[int]]
        """

        if root == None:
            return []

        lst = []
        ans = []
        lst.append(root)

        while lst != []:
            n = len(lst)
            # 每一行新建一个list
            ans.append([])

            for i in range(n):
                node = lst.pop(0)
                ans[-1].append(node.val)
                if node.left != None:
                    lst.append(node.left)
                if node.right != None:
                    lst.append(node.right)

        return ans

题目7: 填充每个节点的下一个右侧节点指针(leetcode)

10.png

11.jpg

思路: 该题将问题即是利用层次遍历,将遍历过程中同一层节点前后关联在一起,需要注意的是除每一行最后一个节点外,其余节点均与下一同行节点关联2-3,4-5,5-6,6-7。如下解法也可以解决leetcode-117题目《填充每个节点的下一个右侧节点指针 II》。该题还有递归遍历解法,详见题目4。

    def connect(self, root):
        """
        :type root: Node
        :rtype: Node
        """

        if root == None:
            return None

        lst = []
        lst.append(root)

        while lst != []:
            n = len(lst)

            for i in range(n):
                node = lst.pop(0)

                if node.left != None:
                    lst.append(node.left)
                if node.right != None:
                    lst.append(node.right)

                #若该节点为该行最后一个节点,则无需设置next指针
                if i == n-1:
                    continue

                # 串联同一行的下一节点
                node.next = lst[0]

        return root