LeetCode 110.平衡二叉树 LeetCode257. 二叉树的所有路径 LeetCode 404.左叶子之和 LeetCode 222.完全二叉树的节点个数

LeetCode 110.平衡二叉树

前序遍历实现

思路:

  • 以统一标尺去求每个处理节点的高度,并比较左右子树的高度差。
  • 进行前序遍历操作,从上到下。整体平衡不代表局部平衡,因此如果左/右子树出现了不平衡现象,也要及时返回不平衡的信息。

如下图,虽整体是平衡的,但在左子节点2和右子节点2下的左子树和右子树是不平衡的。如果此时没及时返回不平衡的信息的话,则会继续向下遍历,那到节点3的时候,该子树已经平衡了,此时就掩盖了左子树不平衡的信息。

手撕Code

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def isBalanced(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: bool
        """

        
        ### 平衡二叉树 是指该树所有节点的左右子树的高度相差不超过 1。
        ### 判断处理节点的左子树高度和右子树高度是否大于1

        
        if not root:
            return True

        #### 单次递归逻辑———— 前序遍历,中左右
        left_tree_height = self.get_Height(root.left)
        right_tree_height = self.get_Height(root.right)

        left_tree = self.isBalanced(root.left)
        if not left_tree:                           ### 左子树存在不平衡
            return False

        right_tree = self.isBalanced(root.right)
        if not right_tree:                          ### 右子树存在不平衡
            return False

        if abs(left_tree_height - right_tree_height) > 1:
            return False
        else:
            return True


    def get_Height(self, root):
        """
        :获得处理节点的左右子树的高度
        :type root: Node
        :rtype: bool
        """

        if not root:
            return 0           ## 确定终止条件

        left_tree_height  = self.get_Height(root.left)      ### 左子树高度
        right_tree_height = self.get_Height(root.right)     ### 右子树高度

        return 1 + max(left_tree_height, right_tree_height) ### 返回处理节点的高度,统一标尺

代码优化:

上述代码的实现步骤为两步:1. 后序遍历求整棵树各节点的高度 2. 前序遍历整颗树判断是否平衡

代码优化的实现步骤为一步,将判断整棵树是否平衡整合到求整棵树各节点的高度中。具体地,在后续遍历求整棵树各节点的高度中,如果已经知道某个节点下已经不平衡的情况下,则直接return一个值用以标识不平衡的信息并向上传递这个不平衡信息,后续在调用该函数的时候只需要判断是否存在这个不平衡信息,通过是否存在不平衡信息来判断树是否平衡。

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def isBalanced(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: bool
        """

        
        ### 平衡二叉树 是指该树所有节点的左右子树的高度相差不超过 1。
        ### 判断处理节点的左子树高度和右子树高度是否大于1

        result = self.get_Height(root)
        if result == -1:
            return False
        else: 
            return True

    def get_Height(self, root):             
        """
        :获得处理节点的左右子树的高度
        :type root: Node
        :rtype: bool
        """

        if not root:
            return 0           ## 确定终止条件

        left_tree_height  = self.get_Height(root.left)      ### 左子树高度
        if left_tree_height == -1 :
            return -1
        right_tree_height = self.get_Height(root.right)     ### 右子树高度
        if right_tree_height == -1:
            return -1

        if abs(left_tree_height - right_tree_height) > 1:   ### 单次递归逻辑,后续遍历实现,从下到上,如果下面已经有不平衡子树了话,就return -1,告诉父节点下面不平衡了
            return -1
        else:
            return 1 + max(left_tree_height, right_tree_height) ### 返回处理节点的高度,统一标尺

LeetCode257. 二叉树的所有路径

涉及到回溯算法,需要理解回溯的思路。

思路:

  • 由于是找根节点到叶子节点的所有路径,从上到下,因此适合于用前序遍历来对树进行遍历操作。
  • 在这个递归的过程中,我们需要从上到下遍历到叶子节点,并通过一个路径变量,来记录从根节点遍历到当前叶子节点的路径,当到了叶子节点之后,再通过另一个结果变量来存储当前的路径变量。随后需要进行回溯,将当前处理节点向上不断进行回溯,直到整棵树遍历完毕。

如下面这颗树,假设处理当前节点的指针为cur,那遍历的过程如表所示
 

cur:1path= [1]
cur:2path = [1,2]
cur:3path = [1,2,3]
cur:7path = [1,2,3,7]
result = [1,2,3,7]由于节点7无左右子节点,因此将path存储到result中
path = [1,2,3]cur为7执行完后,不再进行递归操作,进行回溯,因此path.pop()
cur:8path = [1,2,3,8]result = [1,2,3,8]由于节点8无左右子节点,因此将path存储到result中
path = [1,2,3]cur为8执行完后,不再进行递归操作,进行回溯,因此path.pop()
path = [1,2]cur为3的所有递归操作执行完毕后,进行回溯,path.pop()
path = [1]cur为2的所有递归操作执行完毕后,进行回溯,path.pop()
cur:4path = [1,4]
............
# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def binaryTreePaths(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: List[str]
        """
        
        path = []
        result = []
        
        if not root:
            return result

        self.traversal(root, path, result)
        return result

    def traversal(self, cur, path, result):  #### 要的是根节点到叶子节点的路径,因此适合采用前序遍历。
        """
        :cur    表示当前处理的节点
        : path   用于存储已遍历过的节点
        : result 用于从下到上,来存储从叶子节点开始逐渐pop的节点
        """

        path.append(cur.val)   ### 存储当前节点值

        if not cur.left and not cur.right:      ### 到达叶子节点的情况
            sPath = '->'.join(map(str, path))   ### map(str, path) 是将数组里的每一个元素取出,并转为str类型。join操作将数组转为字符串,'->'.join表示在元素之间添加 -> 符号
            result.append(sPath)               ### 将当前遍历结果存储到result中

            return      ### 确定终止条件      
        
#### 前序遍历的思路,先遍历左子树再遍历右子树
        if cur.left:          ### 有左叶子节点的情况
            self.traversal(cur.left, path, result)
            path.pop()     ### 回溯
        if cur.right:          ### 有右叶子节点的情况
            self.traversal(cur.right, path, result)
            path.pop()     ### 回溯
    
        #### 上述为什么要回溯,是需要重点理解的地方

LeetCode 404.左叶子之和

思路:

  • 类似于判断二叉树是否可对称。需要通过父节点来判断是否左叶子。左叶子之和 = 左子树外侧叶子节点 + 右子树内侧叶子节点
  • 这道题有坑,只需要求左叶子节点的值,因此在求左边的过程中,要判断当前节点是否是叶子节点。

手撕Code:

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def sumOfLeftLeaves(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: int
        """
        
        ### 类似于求二叉树是否可对称.左叶子之和 = 左子树的外侧叶子节点 + 右子树的内测叶子节点
        result = self.get_leftSum(root)
        return result
    
    def get_leftSum(self, root):

        if not root:        ##终止条件
            return 0       
        
        ### 采用后序遍历方法, 左右中,将左子树和右子树的左节点之和返回

        ### 单次递归逻辑 ———— 关键是对左节点的判断,针对右节点只需要让其进行递归进行判断即可
        if root.left:                               ### 存在左子节点
            if not root.left.left and not root.left.right:        ### 不存在子节点的话,当前就是左叶子节点
                left_tree_sum = root.left.val
            else:                                                 ### 当前root.left节点非左叶子节点
                left_tree_sum = self.get_leftSum(root.left)
        else:
            left_tree_sum = 0

        if root.right:                              ### 存在右子节点
            right_tree_sum = self.get_leftSum(root.right)
        else:
            right_tree_sum = 0
        
        left_Sum = left_tree_sum + right_tree_sum
        
        return left_Sum

精简Code:

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def sumOfLeftLeaves(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: int
        """
        
        ### 类似于求二叉树是否可对称.左叶子之和 = 左子树的外侧叶子节点 + 右子树的内测叶子节点
        result = self.get_leftSum(root)
        return result
    
    def get_leftSum(self, root):

        if not root:        ##终止条件
            return 0       
        
        if not root.left and not root.right:        ### 父节点保存左叶子节点的值,这里是叶子节点,因此叶子节点返回的是0
            return 0                

        ### 采用后序遍历方法, 左右中,将左子树和右子树的左节点之和返回

        ### 左遍历
        left_tree_sum = self.get_leftSum(root.left)
        if root.left and not root.left.left and not root.left.right:        ## 左遍历的终止条件:是左叶子节点
             left_tree_sum = root.left.val

        ### 右遍历
        right_tree_sum = self.get_leftSum(root.right)

        left_Sum = left_tree_sum + right_tree_sum
        
        return left_Sum

LeetCode 222.完全二叉树的节点个数

普通二叉树求法:

Version 1: 层序遍历 ———— 迭代法实现。时间复杂度为O(n)

Version 2: 后序遍历 ———— 单层递归逻辑,当前处理节点的节点数目 = 左子树节点数目 + 右子树节点数目 + 1 (1表示的是处理节点本身)

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def countNodes(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: int
        """

### Version 2: 后序遍历实现
        if not root:        ## 处理节点为None
            return 0

        #### 单次递归逻辑 ———— 计算左子节点数 + 计算右子节点数 + 1(当前处理节点数)
        left_tree_sum = self.countNodes(root.left)
        right_tree_sum = self.countNodes(root.right)

        sum_nodes = left_tree_sum + right_tree_sum + 1  ###容易漏了+1,漏了+1的话以为着只处理了底层节点,并将底层节点数目向上传递
        return sum_nodes

注意:

  • return 的值中容易忘了+1,因为是后续遍历,当前处理节点本身存储的是其左右子树节点总数还有其本身。

完全二叉树求法:

注意完全二叉树的概念:如果底层有节点,则一定是从左到右连续分布的。

Version 3: 只求最后一层的节点数目,其余层的节点数目可以通过完全二叉树的特性求得。

思路:

  • 一颗完全二叉树可以拆分为若干个满二叉树。

如图所示:

首先满二叉树的定义是一棵树的节点一定是填满的,因此其节点数目一定符合 (2 ^ 树的深度 - 1)。

  • 对于完全二叉树,其叶子节点一定是从左往右连续分布,因此如果我们通过判断某一颗子树的最左侧深度和最右侧深度,如果深度相同,那么这颗这树一定是满二叉树,如图中1所示。这样的话,我们可以直接通过公式计算出该子树的节点数目,而不用去遍历该子树的所有节点,只需要遍历其最左侧和最右侧。如果深度相同,则可以继续分别往该处理节点的左子节点和右子节点进行递归,一定会存在满二叉树的情况(最差也就是叶子节点)。
  • 对于图中的2,左子树并不是一颗满二叉树,但叶子节点本身也是一颗满二叉树,因此针对叶子节点数目也可以基于满二叉树的逻辑进行实现。
  • 采用后续遍历,将处理节点的左右子树节点数目相加后再加1,即可得到当前处理节点这颗这树的节点数目。

Code:

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def countNodes(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: int
        """

        if not root:
            return 0
        left_nums = self.get_Nums(root.left)
        right_nums = self.get_Nums(root.right)
        sum_nums = left_nums + right_nums
        return sum_nums + 1

    def get_Nums(self, root):
        if not root:        ## 终止条件
            return 0
        
        left_depth, right_depth = 1,1
        left = root.left
        right = root.right
        while left:
            left_depth += 1
            left = left.left

        while right:
            right_depth += 1
            right = right.right

        if left_depth == right_depth:
            nums = 2 ** left_depth - 1
        else:
            nums = self.get_Nums(root.left) + self.get_Nums(root.right) + 1
            
        return nums

不要漏了加上处理节点的数目 (return 记得 +1)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值