代码随想录算法训练营Day13 | Leetcode 110平衡二叉树、257二叉树的所有路径、404左叶子之和、222完全二叉树的节点个数(递归解法)

代码随想录算法训练营Day13 | Leetcode 110平衡二叉树、257二叉树的所有路径、404左叶子之和、222完全二叉树的节点个数(递归解法)

一、翻转二叉树

相关题目:Leetcode110
文档讲解:Leetcode110
视频讲解:Leetcode110

1. Leetcode110.平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。

  • 思路:
    请添加图片描述

    • 递归法:
      • 明确递归函数的参数和返回值
        • 参数:当前传入节点。
        • 返回值:以当前传入节点为根节点的树的高度。
        • 标记左右子树是否差值大于 1:如果当前传入节点为根节点的二叉树已经不是二叉平衡树了,还返回高度的话就没有意义了。所以如果已经不是二叉平衡树了,可以返回 -1 来标记已经不符合平衡树的规则了。
      • 明确终止条件
        递归的过程中依然是遇到空节点了为终止,返回 0,表示当前节点为根节点的树高度为 0。
      • 明确单层递归的逻辑
        分别求出其左右子树的高度,然后如果差值小于等于1,则返回当前二叉树的高度,否则返回 -1,表示已经不是二叉平衡树了。
  • 递归法

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        if self.get_height(root) != -1:
            return True
        else:
            return False

    def get_height(self, root: TreeNode) -> int:
        # Base Case
        if not root:
            return 0
        # 左
        if (left_height := self.get_height(root.left)) == -1:
            return -1
        # 右
        if (right_height := self.get_height(root.right)) == -1:
            return -1
        # 中
        if abs(left_height - right_height) > 1:
            return -1
        else:
            return 1 + max(left_height, right_height)

#精简版:
class Solution:
    def isBalanced(self, root: Optional[TreeNode]) -> bool:
        return self.get_hight(root) != -1
    def get_hight(self, node):
        if not node:
            return 0
        left = self.get_hight(node.left)
        right = self.get_hight(node.right)
        if left == -1 or right == -1 or abs(left - right) > 1:
            return -1
        return max(left, right) + 1

二、二叉树的所有路径

相关题目:Leetcode257
文档讲解:Leetcode257
视频讲解:Leetcode257

1. Leetcode257.二叉树的所有路径

给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
说明:叶子节点 是指没有子节点的节点。

  • 思路:

    • 题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。题目涉及到回溯,因为要把路径记录下来,需要回溯来回退一个路径再进入另一个路径。前序遍历以及回溯的过程如图:
      请添加图片描述
    • 递归法:
      • 递归函数参数以及返回值:要传入根节点,记录每一条路径的 path,和存放结果集的 result,这里递归不需要返回值。
      • 确定递归终止条件
        • 当找到叶子节点,就开始结束的处理逻辑了(把路径放进 result 里),即当 cur 不为空,其左右孩子都为空的时候,就找到叶子节点。(不判断 cur 是否为空)
        • 终止处理:使用 list 结构 path 来记录路径,所以要把 list 结构的 path 转为 string 格式,再把这个 string 放进 result 里。
      • 确定单层递归逻辑
        • 因为是前序遍历,需要先处理中间节点,中间节点就是我们要记录路径上的节点,先放进 path 中。
        • 第一步没有判断 cur 是否为空,那么在这里递归的时候,如果为空就不进行下一层递归了。所以递归前要加上判断语句,保证递归节点非空。
        • 回溯和递归是一一对应的,有一个递归,就要有一个回溯
  • 注意:

    • 递归法+隐形回溯(版本一):
      每次递归调用时传入 path[:] 的拷贝,避免因为回溯或修改同一个列表而导致路径数据混乱。这种方式虽然避免了手动进行回溯(即不需要调用 path.pop()),但可能会产生额外的内存开销。
    • 递归法+隐形回溯(版本二):
      使用 path + ‘->’ 来拼接路径,这样确保了每条路径格式正确,如 “1->2->5”。这种方法相比于使用列表存储路径再转换为字符串的方式,直接利用字符串拼接,使代码更为简洁,但可能在字符串拼接较多时效率略低。
  • 递归法

##递归法+回溯
# Definition for a binary tree node.
class Solution:
    def traversal(self, cur, path, result):
        path.append(cur.val)  # 中
        if not cur.left and not cur.right:  # 到达叶子节点
            sPath = '->'.join(map(str, path))
            result.append(sPath)
            return
        if cur.left:  # 左
            self.traversal(cur.left, path, result)
            path.pop()  # 回溯
        if cur.right:  # 右
            self.traversal(cur.right, path, result)
            path.pop()  # 回溯

    def binaryTreePaths(self, root):
        result = []
        path = []
        if not root:
            return result
        self.traversal(root, path, result)
        return result

##递归法+隐形回溯(版本一)
from typing import List, Optional

class Solution:
    def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
        if not root:
            return []
        result = []
        self.traversal(root, [], result)
        return result
    
    def traversal(self, cur: TreeNode, path: List[int], result: List[str]) -> None:
        if not cur:
            return
        path.append(cur.val)
        if not cur.left and not cur.right:
            result.append('->'.join(map(str, path)))
        if cur.left:
            self.traversal(cur.left, path[:], result)
        if cur.right:
            self.traversal(cur.right, path[:], result)

##递归法+隐形回溯(版本二)
class Solution:
    def binaryTreePaths(self, root: TreeNode) -> List[str]:
        path = ''
        result = []
        if not root: return result
        self.traversal(root, path, result)
        return result
    
    def traversal(self, cur: TreeNode, path: str, result: List[str]) -> None:
        path += str(cur.val)
        # 若当前节点为leave,直接输出
        if not cur.left and not cur.right:
            result.append(path)

        if cur.left:
            # + '->' 是隐藏回溯
            self.traversal(cur.left, path + '->', result)
        
        if cur.right:
            self.traversal(cur.right, path + '->', result)

三、左叶子之和

相关题目:Leetcode404
文档讲解:Leetcode404
视频讲解:Leetcode404

1. Leetcode404.左叶子之和

计算给定二叉树的所有左叶子之和。

  • 思路:

    • 注意是判断左叶子,不是二叉树左侧节点。
    • 左叶子:节点 A 的左孩子不为空,且左孩子的左右孩子都为空(说明是叶子节点),那么 A 节点的左孩子为左叶子节点。
    • 判断当前节点是不是左叶子是无法判断的,必须要通过节点的父节点来判断其左孩子是不是左叶子。
    • 递归法:
      • 确定递归函数的参数和返回值:判断一个树的左叶子节点之和,那么一定要传入树的根节点,递归函数的返回值为数值之和,所以为 int,使用题目中给出的函数就可以了。
      • 确定终止条件:如果遍历到空节点,那么左叶子值一定是 0,注意,只有当前遍历的节点是父节点,才能判断其子节点是不是左叶子。 所以如果当前遍历的节点是叶子节点,那其左叶子也必定是 0。
      • 确定单层递归的逻辑:当遇到左叶子节点的时候,记录数值,然后通过递归求取左子树左叶子之和,和右子树左叶子之和,相加便是整个树的左叶子之和。
  • 注意:
    平时我们解二叉树的题目时,已经习惯了通过节点的左右孩子判断本节点的属性,而本题我们要通过节点的父节点判断本节点的属性。

  • 递归法

##递归
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def sumOfLeftLeaves(self, root):
        if root is None:
            return 0
        if root.left is None and root.right is None:
            return 0
        
        leftValue = self.sumOfLeftLeaves(root.left)  # 左
        if root.left and not root.left.left and not root.left.right:  # 左子树是左叶子的情况
            leftValue = root.left.val
            
        rightValue = self.sumOfLeftLeaves(root.right)  # 右

        sum_val = leftValue + rightValue  # 中
        return sum_val

##递归精简版
class Solution:
    def sumOfLeftLeaves(self, root):
        if root is None:
            return 0
        leftValue = 0
        if root.left is not None and root.left.left is None and root.left.right is None:
            leftValue = root.left.val
        return leftValue + self.sumOfLeftLeaves(root.left) + self.sumOfLeftLeaves(root.right)

四、完全二叉树的节点个数

相关题目:Leetcode222
文档讲解:Leetcode222
视频讲解:Leetcode222

1. Leetcode222.完全二叉树的节点个数

给出一个完全二叉树,求出该树的节点个数。
完全二叉树 的定义如下:
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(从第 0 层开始),则该层包含 1~ 2h 个节点。

  • 思路:

    • 普通二叉树(递归):
      • 确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回以该节点为根节点二叉树的节点数量,所以返回值为 int 类型。
      • 确定终止条件:如果为空节点的话,就返回 0,表示节点数为 0。
      • 确定单层递归的逻辑:先求它的左子树的节点数量,再求右子树的节点数量,最后取总和再加一 (加 1 是因为算上当前中间节点)就是目前节点为根节点的节点数量。
    • 普通二叉树(迭代):利用层序遍历进行迭代计数。
    • 完全二叉树(递归):
      • 完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。
        • 情况一:可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为 1。
        • 情况二:分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况 1 来计算。
          请添加图片描述
      • 判断一个左子树或者右子树是不是满二叉树:在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树,否则便不是。
      • 确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回以该节点为根节点二叉树的节点数量,所以返回值为 int 类型。
      • 确定终止条件:判断其子树是不是满二叉树,如果是则利用公式计算这个子树(满二叉树)的节点数量,如果不是则继续递归。
      • 确定单层递归的逻辑:先求它的左子树的节点数量,再求右子树的节点数量,最后取总和再加一 (加 1 是因为算上当前中间节点)就是目前节点为根节点的节点数量。
  • 复杂度

普通二叉树(递归)普通二叉树(迭代)完全二叉树(递归)
时间复杂度 O ( n ) O(n) O(n) O ( n ) O(n) O(n) O ( log ⁡ n ∗ log ⁡ n ) O(\log n*\log n) O(lognlogn)
空间复杂度 O ( log ⁡ n ) O(\log n) O(logn) O ( n ) O(n) O(n) O ( log ⁡ n ) O(\log n) O(logn)
  • 普通二叉树(递归)
class Solution:
    def countNodes(self, root: TreeNode) -> int:
        return self.getNodesNum(root)
        
    def getNodesNum(self, cur):
        if not cur:
            return 0
        leftNum = self.getNodesNum(cur.left) #左
        rightNum = self.getNodesNum(cur.right) #右
        treeNum = leftNum + rightNum + 1 #中
        return treeNum

#精简版
class Solution:
    def countNodes(self, root: TreeNode) -> int:
        if not root:
            return 0
        return 1 + self.countNodes(root.left) + self.countNodes(root.right)
  • 普通二叉树(迭代)
import collections
class Solution:
    def countNodes(self, root: TreeNode) -> int:
        queue = collections.deque()
        if root:
            queue.append(root)
        result = 0
        while queue:
            size = len(queue)
            for i in range(size):
                node = queue.popleft()
                result += 1 #记录节点数量
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
        return result
  • 完全二叉树(递归)
##写法1
class Solution:
    def countNodes(self, root: TreeNode) -> int:
        if not root:
            return 0
        left = root.left
        right = root.right
        leftDepth = 0 #这里初始为0是有目的的,为了下面求指数方便
        rightDepth = 0
        while left: #求左子树深度
            left = left.left
            leftDepth += 1
        while right: #求右子树深度
            right = right.right
            rightDepth += 1
        if leftDepth == rightDepth:
            return (2 << leftDepth) - 1 #注意(2<<1) 相当于2^2,所以leftDepth初始为0
        return self.countNodes(root.left) + self.countNodes(root.right) + 1

##写法2
class Solution: # 利用完全二叉树特性
    def countNodes(self, root: TreeNode) -> int:
        if not root: return 0
        count = 1
        left = root.left; right = root.right
        while left and right:
            count+=1
            left = left.left; right = right.right
        if not left and not right: # 如果同时到底说明是满二叉树,反之则不是
            return 2**count-1
        return 1+self.countNodes(root.left)+self.countNodes(root.right)  

##写法3
class Solution: # 利用完全二叉树特性
    def countNodes(self, root: TreeNode) -> int:
        if not root: return 0
        count = 0
        left = root.left; right = root.right
        while left and right:
            count+=1
            left = left.left; right = right.right
        if not left and not right: # 如果同时到底说明是满二叉树,反之则不是
            return (2<<count)-1
        return 1+self.countNodes(root.left)+self.countNodes(root.right)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值