代码随想录训练营【二叉树篇】

二叉树

注:本文代码来源于代码随想录

226.翻转二叉树

力扣226

前序或者后序。中序不方便。

Python 递归 前序遍历

# 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 invertTree(self, root: TreeNode) -> TreeNode:
        if not root:
            # 如果root是空
            return None
        root.left, root.right = root.right, root.left
        self.invertTree(root.left)
        # 为什么不需要返回值
        self.invertTree(root.right)
        return root

101. 对称二叉树

力扣101

本质上是判断根节点的左子树和右子树是否可以相互翻转。

本题遍历只能是“后序遍历”,因为我们要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等。

Python

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        if not root:
            # 如果root是空
            return True
        return self.compare(root.left, root.right)
        
    def compare(self, left, right):
        #首先排除空节点的情况
        if left == None and right != None: return False
        elif left != None and right == None: return False
        elif left == None and right == None: return True
        #排除了空节点,再排除数值不相同的情况
        elif left.val != right.val: return False
        
        #此时就是:左右节点都不为空,且数值相同的情况
        #此时才做递归,做下一层的判断
        outside = self.compare(left.left, right.right) #左子树:左、 右子树:右
        inside = self.compare(left.right, right.left) #左子树:右、 右子树:左
        isSame = outside and inside #左子树:中、 右子树:中 (逻辑处理)
        return isSame

104.二叉树的最大深度

力扣104

用后序遍历把孩子的高度返回给中间节点,然后高度加一。

这道题前序比后序代码复杂得多。

Python

# 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 maxDepth(self, root: Optional[TreeNode]) -> int:
        return self.getDepth(root)

    def getDepth(self, node):
        if not node:
            return 0

        leftheight = self.getDepth(node.left)
        rightheight = self.getDepth(node.right)
        height = 1 + max(leftheight, rightheight)
        return height 

111.二叉树的最小深度

力扣111

这道题没有思路。

还是用后序遍历。(前序遍历求深度,后序遍历求高度)(前序遍历代码没有后序遍历代码简洁)

易错点:直接将上一题最大深度的max改成min,是个坑,详见代码随想录

Python

# 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 getDepth(self, node):
        if node is None:
            return 0
        leftDepth = self.getDepth(node.left)  # 左
        rightDepth = self.getDepth(node.right)  # 右
        
        # 当一个左子树为空,右不为空,这时并不是最低点
        if node.left is None and node.right is not None:
            return 1 + rightDepth
        
        # 当一个右子树为空,左不为空,这时并不是最低点
        if node.left is not None and node.right is None:
            return 1 + leftDepth
        
        result = 1 + min(leftDepth, rightDepth)
        return result

    def minDepth(self, root):
        return self.getDepth(root)

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

力扣222

这个题是简单题,伤心了,第二次做还是不会~~(2024.07.29)

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

Python

普通二叉树写法 时间复杂度是O(n)

后续遍历

自己写的时候漏掉了中间节点的1!!!

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

完全二叉树写法 除了底层节点,上面的节点都是满的,底层节点从左到右连续的

有位运算

满二叉树的节点数量是2^h-1

子树不是满二叉树的时候,return的代码总是写错!

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
    # 这是精简的后序遍历写法
    # 子树不是满二叉树的话就要用普通二叉树的方法去计算节点数量(左子树+右子树+1)所以这里应该是node.left而不是已经改变的left,left在while循环调用的过程中已经指向了Null也就是None

110.平衡二叉树

力扣110

第二次做根本想不起来第一次听的思路,伤心啊~~到底该用什么顺序遍历呢?

给定一个二叉树,判断它是否是高度平衡的二叉树。任何一个节点,它的左右子树的高度差小于等于1。

求高度使用后序遍历,求深度使用前序遍历。

使用后序遍历

Python

写得倒是精简,自己写写不到这么精简,尽量写出过程。

正确的代码

# 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
        # 中
        # 如果左右子树都是平衡二叉树,再判断左右子树高度差是不是小于等于1
        if abs(left_height - right_height) > 1:
            return -1
        else:
            return 1 + max(left_height, right_height)

错误的代码

你的代码逻辑是有问题的,因为在 Python 中,False 等于 0。所以当高度为 0 时(例如叶节点的高度),你的逻辑会认为子树是不平衡的。此外,getHeight 函数的返回值应该是高度或一个特定的错误标志(如 -1),而不是布尔值。

class Solution:
    def isBalanced(self, root: Optional[TreeNode]) -> bool:
        if self.getHeight(root) != False:
            return True
        else:
            return False
        

    def getHeight(self, node):
        if not node:
            return 0
        leftheight = self.getHeight(node.left)
        rightheight = self.getHeight(node.right)
        # 左
        if leftheight == False:
            return False
        # 右
        if rightheight == False:
            return False
        # 中
        if abs(leftheight - rightheight) > 1:
            return False
        return 1 + max(leftheight, rightheight)

257. 二叉树的所有路径

这个题还需要重新做。自己写是不会写的。

力扣257

给定一个二叉树,返回所有从根节点到叶子节点的路径。

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

**使用前序遍历。**父节点指向孩子节点。

Python

递归法+回溯

第一轮刷题先不考虑精简写法

# 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 traversal(self, cur, path, result):
        path.append(cur.val)  # 中
        # 中写在终止条件的前面,要不然会漏掉叶子节点。
        if not cur.left and not cur.right:  # 到达叶子节点
            # 为什么条件没有cur不为空?因为下面的逻辑会控制空节点不进入循环。
            sPath = '->'.join(map(str, path))
            result.append(sPath)
            return
        	# 不明白为什么终止条件不需要返回值
            # 加上返回值return result也不算错,不加更简洁。
            """
            主要区别
返回值处理:
return result 被用来返回 result 列表,但在这种上下文中,这并没有实际意义,因为 result 是通过引用传递的,修改它会直接影响调用方。
return 只是简单地结束当前递归调用,不返回任何值,但效果是相同的,因为 result 的修改仍然有效。
为什么两者都对?
result 是通过引用传递的:在 Python 中,当你传递一个列表(如 result)到一个函数时,你传递的是对该列表的引用。这意味着在函数中对 result 的任何修改都会影响原始列表。无论你是否显式地返回 result,它都已经被修改了。

return 只是结束递归调用:在处理完叶子节点并将路径添加到 result 后,显式地 return 可以立即结束当前递归调用。虽然你可以返回 result,但在这种情况下它并没有被使用,因为调用方已经持有 result 的引用。

因此,尽管return result 不必要,但它不会导致错误。不加更为简洁,只是结束当前递归调用。
            """
            
        # 正常来说中应该写在这里,但是本题是例外。
        # 下面的逻辑会控制空节点不进入循环
        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 = []
        """
        在 binaryTreePaths 方法中再次定义 result 和 path 是为了初始化这两个列表。虽然 traversal 方法会使用 path 和 result 参数,但这些参数需要先在 binaryTreePaths 方法中定义并初始化,然后传递给 traversal 方法。这样可以确保每次调用 binaryTreePaths 方法时,result 和 path 都是新的、空的列表,不受之前调用的影响。
        """
        if not root:
            return result
        self.traversal(root, path, result)
        return result

递归法+隐形回溯(版本一) 隐形回溯看不懂

# 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
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)

递归法+隐形回溯(版本二)

# 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 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)

迭代法:

class Solution:

    def binaryTreePaths(self, root: TreeNode) -> List[str]:
        # 题目中节点数至少为1
        stack, path_st, result = [root], [str(root.val)], []

        while stack:
            cur = stack.pop()
            path = path_st.pop()
            # 如果当前节点为叶子节点,添加路径到结果中
            if not (cur.left or cur.right):
                result.append(path)
            if cur.right:
                stack.append(cur.right)
                path_st.append(path + '->' + str(cur.right.val))
            if cur.left:
                stack.append(cur.left)
                path_st.append(path + '->' + str(cur.left.val))

        return result

404.左叶子之和

力扣404

判断当前节点是不是左叶子是无法判断的,必须要通过节点的父节点来判断其左孩子是不是左叶子

后序遍历。一层一层向上返回,后序遍历比较方便。

当遇到左叶子节点的时候,记录数值,然后通过递归求取左子树左叶子之和,和 右子树左叶子之和,相加便是整个树的左叶子之和。

Python

# 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:
            # 遍历到叶子节点,它的左子树左叶子之和为0,右子树左叶子之和也为0,相加为0.
            return 0
        
        leftValue = self.sumOfLeftLeaves(root.left)  # 左
        if root.left and not root.left.left and not root.left.right:  # 左子树是左叶子的情况
            # 为什么要把这种情况单独拿出来,还不清楚。
            """
            只有在左子树是叶子节点时,我们才把它的值加到 leftValue 中。
如果不单独处理这种情况,会导致递归计算所有左子树节点的值,而我们只需要左叶子节点的值。
            """   
            leftValue = root.left.val
            
        rightValue = self.sumOfLeftLeaves(root.right)  # 右

        sum_val = leftValue + rightValue  # 中
        # 理解不了加和,为什么算的是所有左叶子的和
        # 现在理解了,画了个图自己手推看明白了
        return sum_val

513.找树左下角的值

力扣513

我们来分析一下题目:在树的最后一行找到最左边的值

首先要是最后一行,然后是最左边的值。

注意,可能在右子树上,不要漏掉。

本题使用前序中序后序都可以,因为没有中间节点处理逻辑。只要先左后右。优先遍历左侧,左侧没有就去遍历右侧。

在Python中,变量的作用域和可变性是理解代码行为的关键。我们来分析为什么你的两个代码片段中,一个使用self是正确的,而另一个不使用self是错误的。

正确的代码

class Solution:
    def findBottomLeftValue(self, root: TreeNode) -> int:
        self.max_depth = float('-inf') # 用于记录最大深度。
        self.result = None
        self.traversal(root, 0)
        return self.result
    
    def traversal(self, node, depth): # depth用来记录当前遍历的深度。
        if not node.left and not node.right:
            if depth > self.max_depth:
                self.max_depth = depth
                self.result = node.val
            return
        
        if node.left:
            depth += 1
            self.traversal(node.left, depth)
            depth -= 1 # 回溯
            
        if node.right:
            depth += 1
            self.traversal(node.right, depth)
            depth -= 1

错误的代码

class Solution:
    def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
        maxDepth = float('-inf')
        result = None
        self.traversal(root, 0)
        return result 

    def traversal(self, node, depth):
        if not node.left and not node.right:
            if depth > maxDepth:
                maxDepth = depth
                result = node.val 
            return

        if node.left:
            depth += 1
            self.traversal(node.left, depth)
            depth -= 1
        if node.right:
            depth += 1
            self.traversal(node.right, depth)
            depth -= 1

区别与分析

使用 self 的正确代码
  • 类实例变量self.max_depthself.result 是类的实例变量。它们在整个类的范围内都是共享的,并且在递归的每一步中都保持一致。
  • 递归过程中共享状态:在递归调用中,self.max_depthself.result 会被所有递归调用共享,因此每次更新都会反映在整个递归过程中。
  • 确保结果正确self.max_depthself.result 在每次递归调用中都会被正确地更新,确保最终返回的结果是最深的左叶子节点的值。
不使用 self 的错误代码
  • 局部变量maxDepthresult 是局部变量,只在 findBottomLeftValue 方法中有效。
  • 作用域限制:由于 maxDepthresult 只在 findBottomLeftValue 方法的局部作用域中有效,因此在 traversal 方法中更新这些变量并不会影响 findBottomLeftValue 方法中的变量。
  • 更新无效:在递归过程中,maxDepthresult 的更新只影响 traversal 方法中的局部副本,而不会影响 findBottomLeftValue 方法中的局部变量。这导致最终返回的 result 始终为 None,因为它没有被更新。

关键点总结

  • 共享状态:使用 self 定义的变量是类实例的属性,在类的所有方法中共享。
  • 局部变量:在方法中定义的变量是局部变量,只在该方法的作用域内有效。
  • 递归更新:在递归方法中更新类的实例变量可以确保状态在整个递归过程中正确传递和更新。

结论

为了确保在递归过程中共享状态并正确更新最大深度和结果值,必须使用 self 来定义和引用这些变量。这是第一个代码片段正确而第二个代码片段错误的原因。

这两个情况有所不同,关键在于变量在函数调用之间的作用域和生命周期。

257题result = [] 不需要写作 self.result = []

在遍历二叉树路径问题中,我们不需要 resultpath 作为类属性,因为这些变量的作用范围仅限于一次函数调用。每次调用 binaryTreePaths 方法时都会初始化一个新的 result 列表,这个列表会在函数内部传递,并且最终返回给调用者。递归调用中对 result 的修改通过引用传递,不需要作为类属性存在。

class Solution:
    def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
        result = []  # 仅限于 binaryTreePaths 方法的局部变量
        path = []    # 仅限于 binaryTreePaths 方法的局部变量
        if not root:
            return result
        self.traversal(root, path, result)
        return result

    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()

513题result = None 必须写作 self.result = None

在寻找二叉树最底层最左边的节点值时,maxDepthresult 必须作为类属性存在。这是因为我们需要在整个递归过程中持续更新和访问这些变量。递归函数每次调用都需要检查和更新 maxDepthresult,如果它们是局部变量,每次递归调用都会创建新的局部变量,不会反映在整个递归过程的全局状态中。

class Solution:
    def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
        self.maxDepth = float('-inf')  # 类属性,在整个递归过程中共享
        self.result = None             # 类属性,在整个递归过程中共享
        self.traversal(root, 0)
        return self.result

    def traversal(self, node, depth):
        if not node.left and not node.right:
            if depth > self.maxDepth:
                self.maxDepth = depth
                self.result = node.val
            return
        if node.left:
            self.traversal(node.left, depth + 1)
        if node.right:
            self.traversal(node.right, depth + 1)

总结

  • 在遍历二叉树路径的情况下,resultpath 仅在函数调用的局部作用域内使用,不需要跨函数调用共享,因此它们不需要定义为类属性。
  • 在寻找二叉树最底层最左边的节点值的情况下,maxDepthresult 需要在递归过程中保持一致和更新,因此必须定义为类属性。

通过理解变量在递归调用之间的作用域和生命周期,可以决定是否需要将变量定义为类属性。

Python

(版本一)递归法 + 回溯

class Solution:
    def findBottomLeftValue(self, root: TreeNode) -> int:
        self.max_depth = float('-inf') # 用于记录最大深度。
        self.result = None
        self.traversal(root, 0)
        return self.result
    
    def traversal(self, node, depth): # depth用来记录当前遍历的深度。
        if not node.left and not node.right:
            # 没有判断node是否为空,后面的逻辑不会让空节点进入递归循环
            # node是叶子节点
            if depth > self.max_depth:
                self.max_depth = depth
                self.result = node.val
            return
        
        if node.left:
            # node不是叶子节点,node左子树不为空
            # 无需保证右子树为空
            depth += 1
            self.traversal(node.left, depth)
            depth -= 1 # 回溯
            
        if node.right:
            # node不是叶子节点,node右子树不为空
            # 这里代码是对的,最后一行最左边的值可能在右子树上
            depth += 1
            self.traversal(node.right, depth)
            depth -= 1

(版本二)递归法+精简

class Solution:
    def findBottomLeftValue(self, root: TreeNode) -> int:
        self.max_depth = float('-inf')
        self.result = None
        self.traversal(root, 0)
        return self.result
    
    def traversal(self, node, depth):
        if not node.left and not node.right:
            if depth > self.max_depth:
                self.max_depth = depth
                self.result = node.val
            return
        
        if node.left:
            self.traversal(node.left, depth+1)
        if node.right:
            self.traversal(node.right, depth+1)

112. 路径总和

力扣112

递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:

  • 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii)
  • 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (这种情况我们在236. 二叉树的最近公共祖先 (opens new window)中介绍)
  • 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。

可以使用深度优先遍历的方式(本题前中后序都可以,无所谓,因为中节点也没有处理逻辑)来遍历二叉树

不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。

如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。

如果遍历到了叶子节点,count不为0,就是没找到。

Python

(版本一) 递归

# 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 traversal(self, cur: TreeNode, count: int) -> bool:
        if not cur.left and not cur.right and count == 0: # 遇到叶子节点,并且计数为0
            return True
        if not cur.left and not cur.right: # 遇到叶子节点直接返回
            return False
        
        if cur.left: # 左
            count -= cur.left.val
            if self.traversal(cur.left, count): # 递归,处理节点
                return True
            # 如果递归调用返回 True,则返回 True。
			# 如果递归调用返回 False,则回溯,恢复 count。
            count += cur.left.val # 回溯,撤销处理结果
            
        if cur.right: # 右
            count -= cur.right.val
            if self.traversal(cur.right, count): # 递归,处理节点
                return True
            count += cur.right.val # 回溯,撤销处理结果
            
        return False
    
    def hasPathSum(self, root: TreeNode, sum: int) -> bool:
        if root is None:
            return False
        return self.traversal(root, sum - root.val)   
    """
    在 hasPathSum 方法中,我们从根节点开始,并且剩余目标和为 sum - root.val。这是因为在递归遍历中,每次调用都会更新剩余目标和。
例如,如果 sum 是 22,而根节点的值是 5,那么我们需要在后续节点中寻找路径和为 22 - 5 = 17。
    """

(版本二) 递归 + 精简

# 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 hasPathSum(self, root: TreeNode, sum: int) -> bool:
        if not root:
            return False
        if not root.left and not root.right and sum == root.val:
            return True
        return self.hasPathSum(root.left, sum - root.val) or self.hasPathSum(root.right, sum - root.val)
  

113. 路径总和ii

力扣113

Python

class Solution:
    def traversal(self, cur, count, path, result):
        if not cur.left and not cur.right and count == 0:  # 遇到了叶子节点且找到了和为sum的路径
            result.append(list(path)) # 或者是path[:]
            return
        
        if not cur.left and not cur.right:  # 遇到叶子节点而没有找到合适的边,直接返回
            return

        if cur.left:  # 左 (空节点不遍历)
            path.append(cur.left.val)
            count -= cur.left.val
            self.traversal(cur.left, count, path, result)  # 递归
            count += cur.left.val  # 回溯
            path.pop()  # 回溯
        
        if cur.right:  # 右 (空节点不遍历)
            path.append(cur.right.val)
            count -= cur.right.val
            self.traversal(cur.right, count, path, result)  # 递归
            count += cur.right.val  # 回溯
            path.pop()  # 回溯

    def pathSum(self, root, sum):
        result = []
        path = []
        if root is None:
            return result
        path.append(root.val)  # 把根节点放进路径,不放在这里的话就要修改traversal函数
        self.traversal(root, sum - root.val, path, result)
        return result

或者这样写

class Solution:
    def traversal(self, cur, count, path, result):
        path.append(cur.val)  # 把当前节点放进路径
        count -= cur.val
        
        if not cur.left and not cur.right and count == 0:  # 遇到了叶子节点且找到了和为sum的路径
            result.append(list(path))
        
        if cur.left:  # 左 (空节点不遍历)
            self.traversal(cur.left, count, path, result)  # 递归
        
        if cur.right:  # 右 (空节点不遍历)
            self.traversal(cur.right, count, path, result)  # 递归
        
        # 回溯
        count += cur.val
        path.pop()

    def pathSum(self, root, sum):
        result = []
        path = []
        if root is None:
            return result
        self.traversal(root, sum, path, result)
        return result

或者这样写

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

    def traversal(self, cur, count):
        self.path.append(cur.val)  # 把当前节点放进路径
        count -= cur.val
        
        if not cur.left and not cur.right and count == 0:  # 遇到了叶子节点且找到了和为sum的路径
            self.result.append(list(self.path))
        
        if cur.left:  # 左 (空节点不遍历)
            self.traversal(cur.left, count)  # 递归
        
        if cur.right:  # 右 (空节点不遍历)
            self.traversal(cur.right, count)  # 递归
        
        # 回溯
        count += cur.val
        self.path.pop()

    def pathSum(self, root, sum):
        self.result = []
        self.path = []
        if root is None:
            return self.result
        self.traversal(root, sum)
        return self.result

或者这样写

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

    def traversal(self, cur, count):
        """
        在递归方法 traversal 中,程序通过不断递归调用自身来处理左右子树的节点。在每次递归调用结束后,通过回溯操作恢复计数器和路径,以便继续处理其他可能的路径。因为整个树的遍历和路径查找通过递归调用已经完成,所以不需要显式地返回值
        """
        if not cur.left and not cur.right and count == 0:  # 遇到了叶子节点且找到了和为sum的路径
            self.result.append(list(self.path))
        
        if cur.left:  # 左 (空节点不遍历)
            self.path.append(cur.left.val)
            self.traversal(cur.left, count - cur.left.val)  # 递归
            self.path.pop()  # 回溯
        
        if cur.right:  # 右 (空节点不遍历)
            self.path.append(cur.right.val)
            self.traversal(cur.right, count - cur.right.val)  # 递归
            self.path.pop()  # 回溯

    def pathSum(self, root, sum):
        self.result = []
        self.path = []
        if root is None:
            return self.result
        self.path.append(root.val)  # 把根节点放进路径
        self.traversal(root, sum - root.val)
        return self.result

106.从中序与后序遍历序列构造二叉树

力扣106中序后序

力扣105前序中序

有思路但不会写代码……

Python

105.从前序与中序遍历序列构造二叉树

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        # 第一步: 特殊情况讨论: 树为空. 或者说是递归终止条件
        if not preorder:
            return None

        # 第二步: 前序遍历的第一个就是当前的中间节点.
        root_val = preorder[0]
        root = TreeNode(root_val)

        # 第三步: 找切割点.
        separator_idx = inorder.index(root_val)

        # 第四步: 切割inorder数组. 得到inorder数组的左,右半边.
        inorder_left = inorder[:separator_idx]
        inorder_right = inorder[separator_idx + 1:]

        # 第五步: 切割preorder数组. 得到preorder数组的左,右半边.
        # ⭐️ 重点1: 中序数组大小一定跟前序数组大小是相同的.
        preorder_left = preorder[1:1 + len(inorder_left)]
        preorder_right = preorder[1 + len(inorder_left):]

        # 第六步: 递归
        root.left = self.buildTree(preorder_left, inorder_left)
        root.right = self.buildTree(preorder_right, inorder_right)
        # 第七步: 返回答案
        return root

106.从中序与后序遍历序列构造二叉树

class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
        # 第一步: 特殊情况讨论: 树为空. (递归终止条件)
        if not postorder:
            return None

        # 第二步: 后序遍历的最后一个就是当前的中间节点.
        root_val = postorder[-1]
        root = TreeNode(root_val)

        # 第三步: 找切割点.
        separator_idx = inorder.index(root_val)

        # 第四步: 切割inorder数组. 得到inorder数组的左,右半边.
        inorder_left = inorder[:separator_idx]
        inorder_right = inorder[separator_idx + 1:]

        # 第五步: 切割postorder数组. 得到postorder数组的左,右半边.
        # ⭐️ 重点1: 中序数组大小一定跟后序数组大小是相同的.
        postorder_left = postorder[:len(inorder_left)]
        postorder_right = postorder[len(inorder_left): len(postorder) - 1]
        # 这里没有错

        # 第六步: 递归
        root.left = self.buildTree(inorder_left, postorder_left)
        # 新的inorder和postorder是inorder_left和postorder_left
        root.right = self.buildTree(inorder_right, postorder_right)
        # 新的inorder和postorder是inorder_right和postorder_right
         # 第七步: 返回答案
        return root

654.最大二叉树

力扣654

Python

理解是很好理解,写起来怎么就那么烦呢。

(版本一) 基础版

# 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 constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode:
        if len(nums) == 1:
            return TreeNode(nums[0])
        node = TreeNode(0)
        # 找到数组中最大的值和对应的下标
        maxValue = 0
        maxValueIndex = 0
        for i in range(len(nums)):
            if nums[i] > maxValue:
                maxValue = nums[i]
                maxValueIndex = i
        node.val = maxValue
        # 最大值所在的下标左区间 构造左子树
        if maxValueIndex > 0:
            new_list = nums[:maxValueIndex]
            node.left = self.constructMaximumBinaryTree(new_list)
        # 最大值所在的下标右区间 构造右子树
        if maxValueIndex < len(nums) - 1:
            new_list = nums[maxValueIndex+1:]
            node.right = self.constructMaximumBinaryTree(new_list)
        return node
        

(版本二) 使用下标

class Solution:
    def traversal(self, nums: List[int], left: int, right: int) -> TreeNode:
        if left >= right:
            return None
        maxValueIndex = left
        for i in range(left + 1, right):
            if nums[i] > nums[maxValueIndex]:
                maxValueIndex = i
        root = TreeNode(nums[maxValueIndex])
        root.left = self.traversal(nums, left, maxValueIndex)
        root.right = self.traversal(nums, maxValueIndex + 1, right)
        return root

    def constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode:
        return self.traversal(nums, 0, len(nums))

(版本三) 使用切片——————最好理解的

class Solution:
    def constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode:
        if not nums:
            return None
        max_val = max(nums)
        max_index = nums.index(max_val)
        node = TreeNode(max_val)
        node.left = self.constructMaximumBinaryTree(nums[:max_index])
        node.right = self.constructMaximumBinaryTree(nums[max_index+1:])
        return node
        

617.合并二叉树

力扣617

这个数组是从根节点往下一层一层的,题目难度为简单,但我实在没有思路。

其实代码很简单。

Python

(版本一) 递归 - 前序 - 修改root1

很好理解

# 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 mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode:
        # 递归终止条件: 
        #  但凡有一个节点为空, 就立刻返回另外一个. 如果另外一个也为None就直接返回None. 
        if not root1: 
            return root2
        if not root2: 
            return root1
        # 上面的递归终止条件保证了代码执行到这里root1, root2都非空. 
        root1.val += root2.val # 中
        root1.left = self.mergeTrees(root1.left, root2.left) #左
        root1.right = self.mergeTrees(root1.right, root2.right) # 右
        
        return root1 # ⚠️ 注意: 本题我们重复使用了题目给出的节点而不是创建新节点. 节省时间, 空间. 

(版本二) 递归 - 前序 - 新建root

# 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 mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode:
        # 递归终止条件: 
        #  但凡有一个节点为空, 就立刻返回另外一个. 如果另外一个也为None就直接返回None. 
        if not root1: 
            return root2
        if not root2: 
            return root1
        # 上面的递归终止条件保证了代码执行到这里root1, root2都非空. 
        root = TreeNode() # 创建新节点
        root.val += root1.val + root2.val# 中
        root.left = self.mergeTrees(root1.left, root2.left) #左
        root.right = self.mergeTrees(root1.right, root2.right) # 右
        
        return root # ⚠️ 注意: 本题我们创建了新节点. 

700.二叉搜索树中的搜索

力扣700

知道怎么做但写不对

Python

主要是搞清楚终止条件和返回值

class Solution:
    def searchBST(self, root: TreeNode, val: int) -> TreeNode:
        # 为什么要有返回值: 
        #   因为搜索到目标节点就要立即return,
        #   这样才是找到节点就返回(搜索某一条边),如果不加return,就是遍历整棵树了。

        if not root or root.val == val: 
            return root

        if root.val > val: 
            return self.searchBST(root.left, val) # 要return 
        """
        在不带 return 的版本中:
        当递归调用 self.searchBST(root.left, val) 或 self.searchBST(root.right, val) 时,即使找到目标节点,这些调用的返回值不会被传递回原始调用者。
        因此,最终返回的结果可能是 None 或者是不正确的值。
        使用 return 确保每个递归调用的结果都能正确地传递回上一层调用,最终返回正确的搜索结果。
        """

        if root.val < val: 
            return self.searchBST(root.right, val)

98.验证二叉搜索树

力扣98

第二遍了还是没有思路。2024.07.30

要知道中序遍历下,输出的二叉搜索树节点的数值是有序序列。

了这个特性,验证二搜索树,就相当于变成了判断一个序列是不是递增的了

Python

递归法(版本一)利用中序递增性质,转换成数组

# 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 __init__(self):
        self.vec = []

    def traversal(self, root):
        if root is None:
            return
        self.traversal(root.left)
        self.vec.append(root.val)  # 将二叉搜索树转换为有序数组
        self.traversal(root.right)

    def isValidBST(self, root):
        self.vec = []  # 清空数组
        self.traversal(root)
        for i in range(1, len(self.vec)):
            # 注意要小于等于,搜索树里不能有相同元素
            if self.vec[i] <= self.vec[i - 1]:
                return False
        return True

递归法(版本二)设定极小值,进行比较

class Solution:
    def __init__(self):
        self.maxVal = float('-inf')  # 因为后台测试数据中有int最小值

    def isValidBST(self, root):
        if root is None:
            return True
"""
这个地方没有理解透彻,虽然知道应该中序,但是中序的中为什么要这样写?
"""
        left = self.isValidBST(root.left)
        # 中序遍历,验证遍历的元素是不是从小到大
        if self.maxVal < root.val:
            self.maxVal = root.val
        else:
            return False
        right = self.isValidBST(root.right)

        return left and 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 __init__(self):
        self.pre = None  # 用来记录前一个节点

    def isValidBST(self, root):
        if root is None:
            return True

        left = self.isValidBST(root.left)

        if self.pre is not None and self.pre.val >= root.val:
            return False
        self.pre = root  # 记录前一个节点

        right = self.isValidBST(root.right)
        return left and right

530.二叉搜索树的最小绝对差

力扣530

想法:由于是二叉搜索树,任意两个节点的最小绝对差一定是相邻节点的最小绝对差。

Python

递归法(版本一)利用中序递增,结合数组

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

    def traversal(self, root):
        if root is None:
            return
        self.traversal(root.left)
        self.vec.append(root.val)  # 将二叉搜索树转换为有序数组
        self.traversal(root.right)
"""
上面的代码也该背下来了
"""
    def getMinimumDifference(self, root):
        self.vec = []
        self.traversal(root)
        if len(self.vec) < 2:
            return 0
        result = float('inf')
        for i in range(1, len(self.vec)):
            # 统计有序数组的最小差值
            result = min(result, self.vec[i] - self.vec[i - 1])
        return result

递归法(版本二)利用中序递增,找到该树最小值

class Solution:
    def __init__(self):
        self.result = float('inf')
        self.pre = None

    def traversal(self, cur):
        if cur is None:
            return
        self.traversal(cur.left)  # 左
        if self.pre is not None:  # 中
            self.result = min(self.result, cur.val - self.pre.val)
        self.pre = cur  # 记录前一个
        # 为什么放在这里,还没吃透。
        self.traversal(cur.right)  # 右

    def getMinimumDifference(self, root):
        self.traversal(root)
        return self.result

501.二叉搜索树中的众数

力扣501

没有思路,或者说有点思路但不会写代码。

这个题虽然标的简单,要我自己写还是相当困难的。

精髓在于只遍历一遍

Python

递归法(版本一)利用字典

# 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
from collections import defaultdict
"""
collections 模块提供了几个有用的数据结构,可以替代或扩展 Python 内置的数据结构。以下是 collections 模块中可以导入的一些常用类:

deque:双端队列,可以在两端快速添加或删除元素。
namedtuple:创建具有命名字段的元组子类。
Counter:用于计数可哈希对象。
OrderedDict:保持字典的插入顺序(在 Python 3.7 及以上版本中,普通的 dict 也保持插入顺序)。
defaultdict:带有默认值的字典。
ChainMap:将多个字典合并为一个视图。
UserDict:包装字典对象以便于子类化。
UserList:包装列表对象以便于子类化。
UserString:包装字符串对象以便于子类化。
"""

class Solution:
    def searchBST(self, cur, freq_map):
        if cur is None:
            return
        freq_map[cur.val] += 1  # 统计元素频率
        self.searchBST(cur.left, freq_map)
        self.searchBST(cur.right, freq_map)

    def findMode(self, root):
        freq_map = defaultdict(int)  # key:元素,value:出现频率
        result = []
        if root is None:
            return result
        self.searchBST(root, freq_map)
        max_freq = max(freq_map.values())
        for key, freq in freq_map.items():
            if freq == max_freq:
                result.append(key)
        return result

递归法(版本二)利用二叉搜索树性质

class Solution:
    def __init__(self):
        # 可以删除,因为findMode已经重新初始化了这几个属性
        self.maxCount = 0  # 最大频率
        self.count = 0  # 统计频率
        self.pre = None
        self.result = []

    def searchBST(self, cur):
        if cur is None:
            return
        # 注意没有返回值。

        self.searchBST(cur.left)  # 左
        # 中
        if self.pre is None:  # 第一个节点
            self.count = 1
        elif self.pre.val == cur.val:  # 与前一个节点数值相同
            self.count += 1
        else:  # 与前一个节点数值不同
            self.count = 1
        self.pre = cur  # 更新上一个节点

        if self.count == self.maxCount:  # 如果与最大值频率相同,放进result中
            self.result.append(cur.val)

        if self.count > self.maxCount:  # 如果计数大于最大值频率
            self.maxCount = self.count  # 更新最大频率
            self.result = [cur.val]  # 很关键的一步,不要忘记清空result,之前result里的元素都失效了

        self.searchBST(cur.right)  # 右
        return

    def findMode(self, root):
        self.count = 0
        self.maxCount = 0
        self.pre = None  # 记录前一个节点
        self.result = []

        self.searchBST(root)
        return self.result

236. 二叉树的最近公共祖先

力扣236

想不到要用后序遍历要用回溯。知道这个方法还是没有伪代码思路。

需要注意,我们的写法已经包含了p或者q是公共祖先的情况

Python

递归法(版本一)

class Solution:
    def lowestCommonAncestor(self, root, p, q):
        if root == q or root == p or root is None:
            return root
"""如果 root 等于 p 或 q,则返回 root。这意味着我们找到了其中一个节点 p 或 q,那么当前节点就是最低公共祖先。
如果 root 是 None,也返回 None,表示当前子树为空。"""
        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)
        """递归查找左子树,返回结果存储在 left 中。
递归查找右子树,返回结果存储在 right 中。"""

        if left is not None and right is not None:
            """如果 left 和 right 都不为 None,说明 p 和 q 分别在当前节点的左右子树中。因此,当前节点 root 就是它们的最低公共祖先,返回 root。"""
            return root

        if left is None and right is not None:
            return right
        elif left is not None and right is None:
            return left
        """return left:

当左子树中找到了 p 或 q,返回左子树的结果,使得上一层递归调用知道在左子树中找到了目标节点。
return right:

当右子树中找到了 p 或 q,返回右子树的结果,使得上一层递归调用知道在右子树中找到了目标节点。"""
        else: 
            return None
        """如果 left 为 None 且 right 不为 None,说明两个节点都在右子树中,返回右子树的结果 right。
如果 left 不为 None 且 right 为 None,说明两个节点都在左子树中,返回左子树的结果 left。
如果 left 和 right 都为 None,说明当前子树中不包含 p 和 q,返回 None。"""

递归法(版本二)精简

class Solution:
    def lowestCommonAncestor(self, root, p, q):
        if root == q or root == p or root is None:
            return root

        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)

        if left is not None and right is not None:
            return root

        if left is None:
            return right
        return left

235. 二叉搜索树的最近公共祖先

力扣235

需要注意,我们的写法包含全部的情况

稀里糊涂地写对了,但不太清楚返回值相关的地方。

Python

递归法(版本一)

class Solution:
    def traversal(self, cur, p, q):
        if cur is None:
            return cur
                                                        # 中
        if cur.val > p.val and cur.val > q.val:           # 左
            left = self.traversal(cur.left, p, q)
            if left is not None:
                return left

        if cur.val < p.val and cur.val < q.val:           # 右
            right = self.traversal(cur.right, p, q)
            if right is not None:
                return right

        return cur

    def lowestCommonAncestor(self, root, p, q):
        return self.traversal(root, p, q)

递归法(版本二)精简

class Solution:
    def lowestCommonAncestor(self, root, p, q):
        if root.val > p.val and root.val > q.val:
            return self.lowestCommonAncestor(root.left, p, q)
        elif root.val < p.val and root.val < q.val:
            return self.lowestCommonAncestor(root.right, p, q)
        else:
            return root

701.二叉搜索树中的插入操作

力扣701

我们并不需要改变树的结构,只需要在叶子节点插入。

这道题不明白的地方在于为什么直接return而不是return node或者return root

哈哈第二次做了还是分不清什么时候应该有return值什么时候只return。2024.07.30.

Python

递归法(版本一)

没有返回值,需要记录上一个节点(parent),遇到空节点了,就让parent左孩子或者右孩子指向新插入的节点。然后结束递归。

class Solution:
    def __init__(self):
        self.parent = None

    def traversal(self, cur, val):
        if cur is None:
            # 当 cur(当前节点)为空时,说明已经找到适合插入新节点的位置。
            """
            第一遍没有看懂这个地方,其实是终止条件的同时确定是放在当前叶子节点的左子树还是右子树,是作为左叶子还是右叶子。
            """
            node = TreeNode(val)
            if val > self.parent.val:
                self.parent.right = node
            else:
                self.parent.left = node
            return

        self.parent = cur
        if cur.val > val:
            self.traversal(cur.left, val)
        if cur.val < val:
            self.traversal(cur.right, val)

    def insertIntoBST(self, root, val):
        self.parent = TreeNode(0)
        # 将 self.parent 初始化为一个值为 0 的临时节点,以防止在根节点为空时出错。
        # 感觉可以省略?
        if root is None:
            return TreeNode(val)
        # 如果根节点为空,则直接返回一个新创建的值为 val 的节点作为新的根节点。
        self.traversal(root, val)
        return root
    # 调用 traversal 方法在树中找到适当的位置插入新节点,并返回修改后的树的根节点。

递归法(版本二)

class Solution:
    def insertIntoBST(self, root, val):
        if root is None:
            return TreeNode(val)
        parent = None
        cur = root
        while cur:
            parent = cur
            if val < cur.val:
                cur = cur.left
            else:
                cur = cur.right
        if val < parent.val:
            parent.left = TreeNode(val)
        else:
            parent.right = TreeNode(val)
        return root

递归法(版本三)

class Solution:
    def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
        if root is None or root.val == val:
            return TreeNode(val)
        elif root.val > val:
            if root.left is None:
                root.left = TreeNode(val)
            else:
                self.insertIntoBST(root.left, val)
        elif root.val < val:
            if root.right is None:
                root.right = TreeNode(val)
            else:
                self.insertIntoBST(root.right, val)
        return root

递归法(版本四)这个好理解

有返回值,下一层将加入节点返回,本层用root->left或者root->right将其接住

class Solution:
    def insertIntoBST(self, root, val):
        if root is None:
            node = TreeNode(val)
            return node

        if root.val > val:
            root.left = self.insertIntoBST(root.left, val)
        if root.val < val:
            root.right = self.insertIntoBST(root.right, val)

        return root

450.删除二叉搜索树中的节点

力扣450

搜索树的节点删除要比节点增加复杂的多。增加节点可以只增加在叶子节点。删除节点必定会改变树的结构。

有以下五种情况:

  • 第一种情况:没找到删除的节点,遍历到空节点直接返回了
  • 找到删除的节点
    • 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
    • 第三种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
    • 第四种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
    • 第五种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。

Python

递归法(版本一)

class Solution:
    def deleteNode(self, root, key):
        if root is None:
            return root
        if root.val == key:
            if root.left is None and root.right is None:
                return None
            elif root.left is None:
                return root.right
            elif root.right is None:
                return root.left
            # 最后一种情况不好理解
            else:
                cur = root.right
                while cur.left is not None:
                # 首先,将 cur 指向当前节点的右子节点,然后通过 while 循环找到右子树中最左边的节点(即右子树的最小节点)
                    cur = cur.left
                # 此时已经跳出循环,cur.left是空节点。把root的左子树赋值过去 
                cur.left = root.left
                return root.right
            # 为什么是return root.right
            
            
        """
        递归调用将逐步向上返回,每一层递归调用都会返回修改后的子树根节点,最终最顶层的递归调用将返回整个树的根节点。这就是如何通过递归调用来实现返回修改后的树的根节点的。
        """    
        if root.val > key:
            root.left = self.deleteNode(root.left, key)
        if root.val < key:
            root.right = self.deleteNode(root.right, key)
        return root
    # 为什么要return root

递归法(版本二)

class Solution:
    def deleteNode(self, root, key):
        if root is None:  # 如果根节点为空,直接返回
            return root
        if root.val == key:  # 找到要删除的节点
            if root.right is None:  # 如果右子树为空,直接返回左子树作为新的根节点
                return root.left
            cur = root.right
            while cur.left:  # 找到右子树中的最左节点
                cur = cur.left
            root.val, cur.val = cur.val, root.val  # 将要删除的节点值与最左节点值交换
        root.left = self.deleteNode(root.left, key)  # 在左子树中递归删除目标节点
        root.right = self.deleteNode(root.right, key)  # 在右子树中递归删除目标节点
        return root

669. 修剪二叉搜索树

力扣669

给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>=L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。

这里我们为什么需要返回值呢?

因为是要遍历整棵树,做修改,其实不需要返回值也可以,我们也可以完成修剪(其实就是从二叉树中移除节点)的操作。

但是有返回值,更方便,可以通过递归函数的返回值来移除节点。

Python

在遇到不在区间内的节点时,会直接跳到相应的子树处理,减少了不必要的递归调用。

class Solution:
    """
    先判断当前节点是否在区间外(< low 或 > high),如果在区间外,直接递归处理相应的子树(左子树或右子树)。
只有在当前节点值在区间内时,才递归修剪左右子树。"""
    def trimBST(self, root: TreeNode, low: int, high: int) -> TreeNode:
        if root is None:
            return None
        if root.val < low:
            # 当前节点的值小于低限,修剪后的树应该在右子树中寻找
            return self.trimBST(root.right, low, high)
        if root.val > high:
            # 当前节点的值大于高限,修剪后的树应该在左子树中寻找
            return self.trimBST(root.left, low, high)
        # 当前节点的值在区间内,递归修剪左子树和右子树
        root.left = self.trimBST(root.left, low, high)  # root.left 接入符合条件的左孩子
        root.right = self.trimBST(root.right, low, high)  # root.right 接入符合条件的右孩子
        return root
   	 	"""
        修剪函数 self.trimBST 返回的是修剪后的子树的根节点。如果不在 if 条件外更新 root.left 和 root.right,就无法将修剪后的子树正确地连接到当前节点上。
        """

发现这么写也行

对每个节点都先进行完整的左右子树递归,然后才判断是否需要修剪当前节点,因此可能会多进行一些不必要的递归调用。

class Solution:
    """
    不论当前节点值是否在区间内,先递归处理左右子树。
处理完左右子树后再判断当前节点是否在区间外(< low 或 > high),并根据结果决定返回左子树或右子树。"""
    def trimBST(self, root: Optional[TreeNode], low: int, high: int) -> Optional[TreeNode]:
        if root is None:
            return root
        root.left = self.trimBST(root.left, low, high)
        root.right= self.trimBST(root.right, low, high)
        if root.val > high:
            return root.left
        if root.val < low:
            return root.right

        return root

但是这么写不行

class Solution:
    def trimBST(self, root: Optional[TreeNode], low: int, high: int) -> Optional[TreeNode]:
        if root is None:
            return root
        if root.val > high:
            """如果当前节点值大于 high,那么当前节点和其右子树的所有节点都应该被修剪掉,因为它们都大于 high。
正确的处理方法是返回修剪后的左子树(调用 self.trimBST(root.left, low, high)),而不是直接返回 root.left,因为 root.left 可能仍然包含一些需要被修剪的节点。"""
            return root.left
        if root.val < low:
            return root.right
        """当 root.val > high 时,你返回了 root.left,但是这个子树还没有经过修剪,所以有可能包含大于 high 的节点。
当 root.val < low 时,你返回了 root.right,但是这个子树还没有经过修剪,所以有可能包含小于 low 的节点。"""
        root.left = self.trimBST(root.left, low, high)
        root.right= self.trimBST(root.right, low, high)
        return root

108.将有序数组转换为二叉搜索树

力扣108

觉得应该用中序遍历,但具体怎么做没有思路。————其实也不应该用中序2024.07.31

本质就是寻找分割点,分割点作为当前节点,然后递归左区间和右区间

在构造二叉树的时候尽量不要重新定义左右区间数组,而是用下标来操作原数组。

这里定义的是左闭右闭区间,在不断分割的过程中,也会坚持左闭右闭的区间

首先取数组中间元素的位置,不难写出int mid = (left + right) / 2;这么写其实有一个问题,就是数值越界,例如left和right都是最大int,这么操作就越界了,在二分法 (opens new window)中尤其需要注意!

所以可以这么写:int mid = left + ((right - left) / 2);

Python

class Solution:
    def traversal(self, nums: List[int], left: int, right: int) -> TreeNode:
        if left > right:
            return None
        
        mid = left + (right - left) // 2 # 偶数,取靠左边的
        root = TreeNode(nums[mid])
        root.left = self.traversal(nums, left, mid - 1)
        # left是上一步的left,mid - 1是现在的right
        root.right = self.traversal(nums, mid + 1, right)
        return root
    
    def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
        root = self.traversal(nums, 0, len(nums) - 1)
        return root

538.把二叉搜索树转换为累加树

力扣538

感觉应该使用中序遍历右中左。

从树中可以看出累加的顺序是右中左,所以我们需要反中序遍历这个二叉树,然后顺序累加就可以了

代码中为什么没有回溯的明显标志?

  • 递归调用的回溯:每次递归调用 self.traversal(cur.right)self.traversal(cur.left),都会在完成后返回上一层,这就是回溯的本质。
  • 没有显式的回溯标志:递归调用自身的结构隐式地实现了回溯。当右子树遍历完成后,回溯到当前节点并处理,再递归到左子树。
  • 处理顺序:通过反向中序遍历(右-根-左)的顺序,保证了节点值按降序处理,实现了累加树的转换。

尽管代码中没有明显的“回溯”标志,但每次递归调用的返回过程都包含了回溯的操作,从而使算法能够正确地实现二叉搜索树到累加树的转换。

Python

递归法(版本一)

# 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 convertBST(self, root: TreeNode) -> TreeNode:
        self.pre = 0  # 记录前一个节点的累加值,全局变量
        self.traversal(root)
        return root

    def traversal(self, cur):
        if cur is None:
            return        
        self.traversal(cur.right)  # 先遍历右子树
        cur.val += self.pre  # 当前节点值加上之前累加的值
        self.pre = cur.val  # 更新累加值为当前节点值
        self.traversal(cur.left)  # 最后遍历左子树

递归法(版本二)

# 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 __init__(self):
        self.count = 0

    def convertBST(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if root == None:
            return 
        '''
        倒序累加替换:  
        '''
        # 右
        self.convertBST(root.right)

        # 中
        # 中节点:用当前root的值加上pre的值
        self.count += root.val

        root.val = self.count 

        # 左
        self.convertBST(root.left)

        return root 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值