代码随想录刷题 Day20

2 阅读4分钟

调整好状态,重新出发!

平衡二叉树

class Solution:
    def isBalanced(self, node: TreeNode | None) -> bool:
        if not node:
            return True
        def getheight(node:TreeNode)->int:
            if not node:
                return 0
            return max(getheight(node.left),getheight(node.right))+1
        left_balanced=self.isBalanced(node.left)
        right_balanced=self.isBalanced(node.right)
        # 需要检查自己的左右子树高度差
        left_right=abs(getheight(node.left)-getheight(node.right))
        return left_balanced and right_balanced and (left_right<=1)

判断以node为节点的树是否为平衡二叉树需要满足:

  • 左右子树都是平衡二叉树
  • 自己的左右子树高度差的绝对值小于等于1

这道题还有一个比较巧妙的解法,核心思路就是:

后序遍历一边计算高度,一边检查平衡;正常就向上返回高度,异常就向上返回 -1

class Solution:
    def isBalanced(self, node: TreeNode | None) -> bool:
        if not node:
            return True
        def getheight(node:TreeNode)->int:
            if not node:
                return 0
            left_height=getheight(node.left)
            right_height=getheight(node.right)
            if abs(left_height-right_height)>1:
                return -1
            if left_height==-1 or right_height==-1:
                return -1
            else:
                return max(left_height,right_height)+1
        return  getheight(node)!=-1

平衡二叉树要求每个节点的左右子树高度差的绝对值不超过 1。由于判断平衡本身就需要知道左右子树的高度,因此可以在 getheight 递归计算高度的过程中顺便判断当前节点是否平衡。对于每个节点,先递归获得左右子树的高度;如果某个子树已经返回 -1,说明该子树内部已经不平衡,则当前节点也继续返回 -1。如果左右子树都正常,再判断它们的高度差是否超过 1,若超过则返回 -1 作为“不平衡”的故障信号;否则返回当前节点的正常高度 max(left, right) + 1。这样一旦某处出现不平衡,-1 就会沿递归调用逐层向上传递到根节点。

二叉树的所有路径

# 力扣不通过
class Solution:
    def binaryTreePaths(self, node: TreeNode, path=[], result=[]):
        path.append(node.val)
        if not node.left and not node.right:
            result.append('->'.join(map(str,path)))
        if node.left:
            self.binaryTreePaths(node.left,path,result)
            path.pop()
        if node.right:
            self.binaryTreePaths(node.right,path,result)
            path.pop()
        return result

这个逻辑是没有问题的。

  1. 递归函数的参数是节点,路径,结果
  2. 终止条件是是叶子节点,将这条路径添加到结果里
  3. 启动递归,前序遍历(中添加数值到path,递归左+回溯,递归右+回溯)
class Solution:
    def binaryTreePaths(self, node: TreeNode, path=None, result=None):
        if path is None: #不能写not path,因为包含了列表为空的情况
            path=[]
        if result is None:
            result=[]
        path.append(node.val)
        if not node.left and not node.right:
            result.append('->'.join(map(str,path)))
        if node.left:
            self.binaryTreePaths(node.left,path,result)
            path.pop()
        if node.right:
            self.binaryTreePaths(node.right,path,result)
            path.pop()
        return result

问题出现在python的特性上 比如:

def f(a=[]):
    a.append(1)
    print(a)

第一次调用:

f()

因为你没传 a,Python 会自动使用默认值 []

问题在于:这个 []函数定义时就创建好的同一个列表

所以第一次:

默认列表原来是 []
append(1)
变成 [1]

第二次再:

f()

Python 确实又“自动使用默认参数”,但它用的是同一个默认列表,现在它已经是:

[1]

所以再 append:

[1, 1]

因此:

f()
f()
f()

输出:

[1]
[1, 1]
[1, 1, 1]

你可以把它理解成:

# 大概类似于 Python 在函数定义时偷偷做了
默认a = []

def f():
    a = 默认a

每次 f() 没传参数时,都是去拿这个 默认a,而不是重新执行一次:

a = []

所以一句话记:

默认参数会在没传参时自动使用,但默认对象本身不会每次重新创建。

这也是为什么通常写:

def f(a=None):
    if a is None:
        a = []

因为这里的:

a = []

是在每次调用函数时真正执行的,所以每次都会创建新列表。

左叶子之和

class Solution:
    def sumOfLeftLeaves(self, node: Optional[TreeNode]) -> int:
        if node==None:
            return 0
        leftvalue=self.sumOfLeftLeaves(node.left)
        if node.left and not node.left.left and not node.left.right:
            leftvalue=node.left.val
        rightvalue=self.sumOfLeftLeaves(node.right)
        result= leftvalue + rightvalue
        return result

这道题的话首先需要知道什么是左叶子。如果节点的左孩子是叶子节点,那么这个左孩子就是左叶子

然后就是后序遍历的处理逻辑了。

我发现二叉树的题,做到目前为止大致两种:

  1. 需要遍历所有的节点,前序遍历+处理
  2. 需要左右孩子的结果汇总到父节点,后续遍历+处理

就是所谓的递归,基本都是建立在遍历上的。

完全二叉树的节点个数

常规解法

用后序遍历,遍历所有的节点

class Solution:
    def countNodes(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0
        leftcount=self.countNodes(root.left)
        rightcount=self.countNodes(root.right)
        total_count=leftcount+rightcount+1
        return total_count

可以实现功能,但是力扣没法通过,是需要有简化的方法的。

利用完全二叉树和满二叉树的关系

在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树。

class Solution:
    def countNodes(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0
        left=root.left
        right=root.right
        left_depth=1
        right_depth=1
        while left:
            left=left.left
            left_depth+=1
        while right:
            right=right.right
            right_depth+=1
        if left_depth==right_depth:
            return 2**left_depth-1
        return self.countNodes(root.left)+self.countNodes(root.right)+1

这题到时候回来再做一下,每台搞懂。我现在需要把之前二叉树的题再复习一下,递归这一块还是比较抽象