【代码随想录day15】110.平衡二叉树 ● 257. 二叉树的所有路径 ● 404.左叶子之和

本文介绍了如何判断一个二叉树是否是平衡的,通过递归计算节点的最大深度,并使用海象表达式优化代码。同时,讨论了如何找出二叉树的所有路径,使用递归和回溯策略。最后,讲解了计算二叉树左叶子之和的方法,通过递归遍历并判断节点的左右子树。
摘要由CSDN通过智能技术生成

110.平衡二叉树

问题

题目链接:https://leetcode.cn/problems/balanced-binary-tree/
给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
示例:
在这里插入图片描述

输入:root = [1,2,2,3,3,null,null,4,4]
输出:false

解答

这里强调一波概念:

二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。
二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数。

先用递归写一个返回最大深度的函数,然后再用递归判断根节点的左右子树是否相差大于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 maxnode(self, root):
        if not root: return 0
        return 1+max(self.maxnode(root.left), self.maxnode(root.right))


    def isBalanced(self, root: Optional[TreeNode]) -> bool:
        if not root:
            return True
        a = abs(self.maxnode(root.left) - self.maxnode(root.right))
        if a > 1:return False
        return self.isBalanced(root.left) and self.isBalanced(root.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: TreeNode) -> bool:
        return self.height(root) != -1
    def height(self, node: TreeNode) -> int:
            if not node:
                return 0
            left = self.height(node.left)
            if left == -1:
                return -1
            right = self.height(node.right)
            if right == -1 or abs(left - right) > 1:
                return -1
            return max(left, right) + 1

此处用了python3.8的海象表达式(walrus operator): if (left_height := self.get_height(root.left)) == -1:
海象表达式(Walrus operator)是Python 3.8版本引入的一种新的语法特性。它使用了:=这个符号,允许在表达式中进行变量的赋值操作。

通常情况下,Python中的变量赋值是在等号的左侧进行的,例如x = 10。而海象表达式允许我们在表达式的右侧进行变量赋值,将结果赋给一个新的变量。这对于简化代码和增加可读性非常有用。
一个常见的用法是在条件语句中,例如:

if (n := len(my_list)) > 10:
    print(f"List length is {n} which is greater than 10.")

在上面的代码中,海象表达式(n := len(my_list))用于同时计算列表的长度并将结果赋值给变量n。然后,我们可以直接在条件语句中使用变量n进行比较操作。
海象表达式还可以在其他情况下使用,例如循环语句和列表推导式中,以简化代码和提高可读性。
需要注意的是,海象表达式是在Python 3.8版本中引入的新特性,因此在较旧的Python版本中不可用。在使用海象表达式时,需要确保代码运行在支持该特性的Python版本上。

迭代思路也类似,先用一个函数求最大高度,然后采用栈模拟后续遍历判断每个节点的子树是否平衡。
当然此题用迭代法,其实效率很低,因为没有很好的模拟回溯的过程,所以迭代法有很多重复的计算。

class Solution:
    def isBalanced(self, root: Optional[TreeNode]) -> bool:
        if not root:
            return True

        height_map = {}
        stack = [root]
        while stack:
            node = stack.pop()
            if node:
                stack.append(node)
                stack.append(None)
                if node.left: stack.append(node.left)
                if node.right: stack.append(node.right)
            else:
                real_node = stack.pop()
                left, right = height_map.get(real_node.left, 0), height_map.get(real_node.right, 0)
                if abs(left - right) > 1:
                    return False
                height_map[real_node] = 1 + max(left, right)
        return True

257. 二叉树的所有路径

问题

题目链接:https://leetcode.cn/problems/binary-tree-paths/
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。

叶子节点 是指没有子节点的节点。
示例:
在这里插入图片描述

输入:root = [1,2,3,null,5]
输出:[“1->2->5”,“1->3”]

解答

自己想这题的时候思路很简单,递归只需要判断是否为叶子节点就行,不是叶子节点则进入下一层,即该节点的子节点。但会遇到一个问题,那就是如何复制已存在的路径,看了解答才知道可以用copy package,那么题目就很简单了。

# 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
# copy可以用来复制路径,避免对上一级的path产生影响
import copy

class Solution:
    def markpath(self, root, path, result):
        if not root: return None
        path.append(root.val)
        if not root.left and not root.right:
            result.append('->'.join(map(str, path))) #map(fucntion, iteratable)
        if root.left:
            self.markpath(root.left, copy.copy(path), result)
        if root.right:
            self.markpath(root.right, copy.copy(path), result)
        # copy函数是复制了一个path,而不是在原path上修改,因此回不回溯都不影响
        #path.pop()

    def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
        if not root: return []
        result = []
        self.markpath(root, [], result)
        return result

但是由于python里有copy库,因此是否回溯就不重要了,因为永远不会对上一级的path产生影响,所以以下补上C++里的方法,帮助更好理解回溯

这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。

在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一个路径再进入另一个路径。

前序遍历以及回溯的过程如图:
在这里插入图片描述

在这里插入图片描述
与python里不同,c++没有copy函数复制path,因此需要采用一个vector来记录path,所以要把vector 结构的path转为string格式,再把这个string 放进 result里。同时当遇到叶子节点的时候,即递归结束,要开始回溯了,方法就是删除path最后一个元素。同时要注意,回溯和递归是一一对应的,有一个递归,就要有一个回溯,即递归要和回溯在同一个括号内。

class Solution {
private:

    void traversal(TreeNode* cur, vector<int>& path, vector<string>& result) {
        path.push_back(cur->val); // 中,中为什么写在这里,因为最后一个节点也要加入到path中 
        // 这才到了叶子节点
        if (cur->left == NULL && cur->right == NULL) {
            string sPath;
            for (int i = 0; i < path.size() - 1; i++) {
                sPath += to_string(path[i]);
                sPath += "->";
            }
            sPath += to_string(path[path.size() - 1]);
            result.push_back(sPath);
            return;
        }
        if (cur->left) { // 左 
            traversal(cur->left, path, result);
            path.pop_back(); // 回溯
        }
        if (cur->right) { // 右
            traversal(cur->right, path, result);
            path.pop_back(); // 回溯
        }
    }

public:
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> result;
        vector<int> path;
        if (root == NULL) return result;
        traversal(root, path, result);
        return result;
    }
};

补充:
python其实也可以不用copy函数,同样可以与c++对应,修改如下:

from typing import List, Optional

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

再修改一下:
from typing import List, Optional

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

模拟一下下图的过程吧:
在这里插入图片描述

首先定义一下:if1,if2,if3分别表示generate_paths里的三个判断if,以及下文的第几个递归表示序号行执行的递归,不表示实际递归层数。

  1. cur=1,path=[1],满足if2,进入if2的下一层递归2
  2. cur=2,path=[1,2],满足if2,进入if2的下一层递归3
  3. cur=3,path=[1,2,3],满足if1,输出路径,且不满足if2,if3,因此第3个递归执行结束
  4. 第3个递归执行完毕,回到第2个递归,执行剩下的path.pop(),path=[1,2]
  5. 但此时第2个递归还没结束,因为也满足if3
  6. cur=2,path=[1,2],满足if3,进入if3的下一层递归
  7. cur=4,path=[1,2,4],满足if2,进入if2的下一层递归
  8. cur=5,path=[1,2,4,5],满足if1,输出路径
  9. 第8个递归执行完毕,回到上一层递归,即第7个递归,执行剩下的pop,path=[1,2,4]
  10. cur=4,path=[1,2,4],满足if3,进入if3的下一层递归
  11. cur=6,path=[1,2,4,6],满足if1,输出路径
  12. 第11个递归执行结束,退回到第10个,执行剩下的pop,path=[1,2,4]
  13. 由于第10个递归if3也判断完毕,因此第10个递归执行结束,退回上一层即第6,执行pop,path=[1,2]
  14. 同理第6也执行完毕,退回到第1个,执行pop,path=[1]
  15. cur=1,path=[1],满足if3,进入if3的下一层递归,之后的右子树同理,输出path=[1,7,8]后不断回溯到第1个迭代,由于此时cur=root=1的左右节点都处理完了,因此整个递归过程结束。

顺便用chatGPT解释了一下为啥要path.pop():
在给左子树递归调用 generate_paths 之后,需要回溯到上一层继续遍历其他分支。由于 path 列表是作为参数传递的,如果不进行回溯操作,那么左子树的遍历过程中添加的节点值将会一直保留在 path 列表中,影响到右子树的遍历。

通过调用 path.pop(),可以在递归回溯到上一层时,将 path 列表中的当前节点值移除,恢复到上一层的状态。这样,在处理右子树时,就能够从一个空的 path 列表开始,不受之前遍历过的节点的影响。

简而言之,path.pop() 的作用是确保在回溯到上一层时,将当前节点从路径列表中移除,以便在处理其他分支时得到正确的路径。

404.左叶子之和

问题

题目链接:https://leetcode.cn/problems/sum-of-left-leaves/
计算给定二叉树的所有左叶子之和。
示例:
在这里插入图片描述

输入: root = [3,9,20,null,null,15,7]
输出: 24
解释: 在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24

解答

这道题目要求左叶子之和,其实是比较绕的,因为不能判断本节点是不是左叶子节点。此时就要通过节点的父节点来判断其左孩子是不是左叶子了。
此时有两个判断条件,首先该节点没有子节点,然后该节点是上一个节点的左节点。因此判断停止条件是not root.left.left and not root.left.right。但是停止之后返回什么呢,肯定不能只返回root.left.val,因为如果该root存在右节点,我们并没有对右节点的进行判断,因此我们将root.right作为下一层的root继续输入,因此我们要返回root.left.val+function(root.right)

# 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: Optional[TreeNode]) -> int:
        if not root: return 0
        # 排除没有左节点的情况,但是右节点没判断
        # 因此我们返回右子树的所有左叶子的和
        if not root.left: return self.sumOfLeftLeaves(root.right)
        # 同理,右节点没判断,因此加上右子树的所有左叶子和
        if (not root.left.left) and (not root.left.right):
            return root.left.val + self.sumOfLeftLeaves(root.right)
        return self.sumOfLeftLeaves(root.left)+self.sumOfLeftLeaves(root.right)

用chatGPT解释了一下上述代码:
这段代码实现了计算二叉树中所有左叶子节点值的和。下面是代码的解释:

1.首先,检查根节点是否为空,如果为空,表示当前子树为空树,直接返回0作为和的初始值。

2.然后,检查根节点的左子节点是否为空,如果为空,表示当前子树没有左子树,那么就递归地计算右子树中的左叶子节点值的和(通过调用 self.sumOfLeftLeaves(root.right))。

3.如果根节点的左子节点存在,再进一步检查左子节点是否为叶子节点(即没有左子节点和右子节点)。如果是叶子节点,那么将左子节点的值加上右子树中左叶子节点值的和,作为最终的和。

4.如果根节点的左子节点不是叶子节点,那么就递归地计算左子树和右子树中左叶子节点值的和,并将两者的和返回。

这段代码利用了递归的思想,通过不断地将问题拆分成更小的子问题来求解。它按照以下步骤计算左叶子节点值的和:

如果当前子树为空树,则和为0。
如果当前子树没有左子树,则计算右子树中的左叶子节点值的和。
如果当前子树有左子树,并且左子节点为叶子节点,则将左子节点的值加上右子树中左叶子节点值的和,作为最终的和。
如果当前子树有左子树,并且左子节点不是叶子节点,则分别计算左子树和右子树中左叶子节点值的和,并将两者的和返回。
通过递归地处理每个节点,直到遍历完整棵二叉树,最终得到所有左叶子节点值的和作为结果返回。

随想录上的结构更清晰一些:

class Solution:
    def sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0
        
        # 检查根节点的左子节点是否为叶节点
        if root.left and not root.left.left and not root.left.right:
            left_val = root.left.val
        else:
            left_val = self.sumOfLeftLeaves(root.left)
            
        # 递归地计算右子树左叶节点的和
        right_val = self.sumOfLeftLeaves(root.right)
        
        return left_val + right_val

迭代法思路一样,只不过用循环和栈实现:

class Solution:
    def sumOfLeftLeaves(self, root: TreeNode) -> int:
        """
        Idea: Each time check current node's left node. 
              If current node don't have one, skip it. 
        """
        stack = []
        if root: 
            stack.append(root)
        res = 0
        
        while stack: 
            # 每次都把当前节点的左节点加进去. 
            cur_node = stack.pop()
            if cur_node.left and not cur_node.left.left and not cur_node.left.right: 
                res += cur_node.left.val
                
            if cur_node.left: 
                stack.append(cur_node.left)
            if cur_node.right: 
                stack.append(cur_node.right)
                
        return res
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值