代码随想录算法训练营Day17 | 110.平衡二叉树 | 257. 二叉树的所有路径 | 404.左叶子之和

110. 平衡二叉树

题目链接 | 解题思路

一棵高度平衡二叉树定义为:一个二叉树每个节点的左右两个子树的高度差的绝对值不超过 1 。

暴力解法很容易想到,计算每个节点的高度,然后遍历所有节点,如果发现不平衡直接返回。

但是对于不平衡的输入会由很多不必要的复杂度(时间或者空间)。实际上在计算节点高度的时候,已经可以进行平衡判断了。

暴力解法

class Solution:
    def height(self, node: Optional[TreeNode]) -> int:
        if node == None:
            return 0
        left_height = self.height(node.left)        # left
        right_height = self.height(node.right)      # right
        return 1 + max(left_height, right_height)   # middle


    def isBalanced(self, root: Optional[TreeNode]) -> bool:
        if root == None:
            return True
        left_height = self.height(root.left)
        right_height = self.height(root.right)
        if abs(left_height - right_height) > 1:
            return False
        return self.isBalanced(root.left) and self.isBalanced(root.right)

巧妙解法

class Solution:
    def height(self, node: Optional[TreeNode]) -> int:
        if node == None:
            return 0
        left_height = self.height(node.left)        # left
        right_height = self.height(node.right)      # right
        if left_height < 0 or right_height < 0:
            return -1
        if abs(left_height - right_height) > 1:
            return -1
        return 1 + max(left_height, right_height)
        

    def isBalanced(self, root: Optional[TreeNode]) -> bool:
        return self.height(root) != -1

257. 二叉树的所有路径

题目链接 | 解题思路

非常符合直觉的前序遍历,记录当前的 path,在遍历中不断更新,直到碰到叶子节点的时候将整条 path 添加到结果中。

但是与之前题目的区别在于,用于记录当前路径的 path 数组是会不断更新的,也就是说在执行完左子树的递归后,path 已经发生了改变,没办法直接再将其传入右子树的递归。

这里就需要用到回溯的思想。

每一次递归都要和回溯绑定使用,才能保证回溯有效!

暴力解法

朴素的前序遍历,通过递归时的输入传递当前节点的 path(不包括当前节点),最后返回结果。为了保证 path 中记录的有效性,递归时传入的是 path.copy()
要注意数组的复制。这里用的一维数组使用浅复制即可,如果是多层数组则需要用到深复制(copy.deepcopy(a))。
这个方法的缺点是需要不停进行数组的复制,空间复杂度有点高。

class Solution:
    def __init__(self):
        self.results = []

    def path(self, node: TreeNode, path: List[str]) -> None:
        if node != None:                    # do nothing over None node
            path.append(str(node.val))
            if node.left == None and node.right == None:        # leaf node, add to results
                self.results.append("->".join(path))
            else:
                self.path(node.left, path.copy())
                self.path(node.right, path.copy())

    def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
        self.path(root, [])
        return self.results

回溯解法

与之前的解法唯一的区别在于,递归时直接传入了 path 数组,并且在当前递归执行完毕后进行了一次 pop。这保证了在进行下一次递归(不管是当前的右子树,或者是)的时候,path 数组会保持进行当前递归前的状态(就像是当前数组的 copy 一样)。

重点:回溯和递归是一一对应的,有一个递归,就要有一个回溯。同时也只需要有一个回溯,因为可以保证每一次调用递归都进行了一层回溯,所以当前的递归结束后,每次深层递归导致的改动都被回溯了。
如下图所示:
在这里插入图片描述

class Solution:
    def __init__(self):
        self.results = []

    def path(self, node: TreeNode, path: List[str]) -> None:
        path.append(str(node.val))
        if node.left == None and node.right == None:        # leaf node, add to results
            self.results.append("->".join(path))
        else:
            if node.left != None:
                self.path(node.left, path)
                path.pop()
            if node.right != None:
                self.path(node.right, path)
                path.pop()

    def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
        if root == None:
            return []
        self.path(root, [])
        return self.results

404. 左叶子之和

题目链接 | 解题思路

题目中“左叶子”的定义:一个叶子节点,同时是另一个节点的左节点。
很明显这是一个 bottom up 的过程,所以应该是后序遍历,从子节点收集数据然后回传给父节点。
需要注意的是递归的单层逻辑中,判断当前节点的左节点是否是叶子并不属于终止条件,而是决定了回传的值,属于是“中”的部分。

class Solution:
    def sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:
        if root == None:
            return 0
        count = 0
        if root.left != None and root.left.left == None and root.left.right == None:
            count += root.left.val
        left_sub_sum = self.sumOfLeftLeaves(root.left)
        right_sub_sum = self.sumOfLeftLeaves(root.right)
        return left_sub_sum + right_sub_sum + count
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值