代码随想录算法训练营day21(python版)|第六章 二叉树part08|669. 修剪二叉搜索树`108.将有序数组转换为二叉搜索树`538.把二叉搜索树转换为累加

目录

669. 修剪二叉搜索树

思路梳理(抓住 BST 性质)

解法一:递归

解法二:迭代(不占递归栈,更贴工程)

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

思路梳理(简明)

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

解法一:递归反向中序(直观,推荐)

解法二:迭代(显式栈,避免递归栈)

总结篇


669. 修剪二叉搜索树

这道题目比较难,比 添加增加和删除节点难的多,建议先看视频理解。

题目链接/文章讲解: 代码随想录

视频讲解: 你修剪的方式不对,我来给你纠正一下!| LeetCode:669. 修剪二叉搜索树_哔哩哔哩_bilibili

思路梳理(抓住 BST 性质)

设区间是 [low, high](闭区间,包含边界):

  • root.val < low
    root 以及它的整棵左子树都比 low 小,不可能保留;
    只可能在 右子树里找到可保留的节点 ⇒ 直接返回 trim(root.right)

  • root.val > high
    root 以及它的整棵右子树都比 high 大,也不可能保留;
    只可能在 左子树里找到可保留的节点 ⇒ 直接返回 trim(root.left)

  • low ≤ root.val ≤ high
    root 可以保留,但左右子树仍需分别修剪:
    root.left = trim(root.left)root.right = trim(root.right),最后返回 root

时间复杂度O(n)(每个节点最多访问一次)
空间复杂度:递归 O(h),迭代 O(1)h 为树高,平衡树约 log n,最坏 n

解法一:递归
  • root.val < low ⇒ 左子树所有值 < root.val < low全都不合格,只去右边找。

  • root.val > high ⇒ 右子树所有值 > root.val > high全都不合格,只去左边找。

# 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 trimBST(self, root: 'TreeNode', low: int, high: int) -> 'TreeNode':
        """
        递归修剪:
        - 利用 BST 性质判断当前节点与区间 [low, high] 的关系,
          决定“整棵丢弃/保留并继续修剪哪一边/两边”。
        """
        if root is None:
            return None

        # 情况1:当前值过小 -> 当前节点及其左子树都不可能在区间内,去右子树找
        if root.val < low:
            return self.trimBST(root.right, low, high)

        # 情况2:当前值过大 -> 当前节点及其右子树都不可能在区间内,去左子树找
        if root.val > high:
            return self.trimBST(root.left, low, high)

        # 情况3:当前值在区间内 -> 当前节点保留,但左右子树需要继续修剪
        root.left = self.trimBST(root.left, low, high)
        root.right = self.trimBST(root.right, low, high)
        return root
解法二:迭代(不占递归栈,更贴工程)

迭代思路三步走(利用指针“往下走并就地修剪”):

  1. 把根挪进区间
    root.val < low,根换成 root.right;若 root.val > high,根换成 root.left;直到根在区间内或树为空。

  2. 修剪左侧(去掉所有 < low 的左链节点)
    从根一路向左:

    当发现 cur.left 存在且 cur.left.val < low,就把 cur.left 替换为 cur.left.right(因为 cur.left 以及它的整棵左子树都小于 low,只有它的右子树还有可能落在区间内)。
  3. 修剪右侧(去掉所有 > high 的右链节点)
    从根一路向右:

    当发现 cur.right 存在且 cur.right.val > high,就把 cur.right 替换为 cur.right.left(对称地,cur.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 trimBST(self, root: 'TreeNode', low: int, high: int) -> 'TreeNode':
        """
        迭代修剪:
        1) 先把 root 调整到区间内
        2) 修剪左侧:移除所有 < low 的左孩子(用其右孩子顶替)
        3) 修剪右侧:移除所有 > high 的右孩子(用其左孩子顶替)
        """
        # 1) 根不在区间内时,不断向下“跳过”整侧子树,直到根在区间内或为空
        while root and (root.val < low or root.val > high):
            if root.val < low:
                root = root.right    # 左边整棵都太小,跳到右边
            else:  # root.val > high
                root = root.left     # 右边整棵都太大,跳到左边

        if not root:
            return None  # 整棵树都不在区间内

        # 2) 修剪左侧:消除所有 < low 的左孩子
        cur = root
        while cur:
            # 当左孩子存在且过小时,用“左孩子的右子树”来顶替它
            while cur.left and cur.left.val < low:
                cur.left = cur.left.right
            cur = cur.left  # 继续沿着左链向下检查

        # 3) 修剪右侧:消除所有 > high 的右孩子
        cur = root
        while cur:
            # 当右孩子存在且过大时,用“右孩子的左子树”来顶替它
            while cur.right and cur.right.val > high:
                cur.right = cur.right.left
            cur = cur.right  # 继续沿着右链向下检查

        return root

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

本题就简单一些,可以尝试先自己做做。

代码随想录

视频讲解:构造平衡二叉搜索树!| LeetCode:108.将有序数组转换为二叉搜索树_哔哩哔哩_bilibili

思路梳理(简明)
  • 输入是递增有序数组 nums

  • 选中间位置 mid 作为根:root = TreeNode(nums[mid])

  • 左半段 [l, mid-1] 递归建左子树,右半段 [mid+1, r] 递归建右子树。

  • l > r 时子树为空,返回 None

  • 中点选择:mid = (l + r) // 2(或 (l + r + 1) // 2 都行,差别很小)。

为什么“近似平衡”:每层都把区间对半切,因此树高接近 log n
复杂度:时间 O(n)(每个元素建一次节点),空间 O(log n)(递归栈,平均情况)。

# 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 sortedArrayToBST(self, nums: list[int]) -> 'TreeNode':
        """
        思路:分治 + 递归
        - 取中点为根,左边建左子树,右边建右子树
        - 区间为空时返回 None
        - 因为每次都尽量对半拆,得到的 BST 近似平衡
        """

        def build(l: int, r: int) -> 'TreeNode | None':
            # 区间为空 -> 空子树
            if l > r:
                return None

            # 选中点为根(取左中点/右中点都可以)
            mid = (l + r) >> 1  # 等价于 (l + r) // 2
            root = TreeNode(nums[mid])

            # 递归构建左右子树
            root.left = build(l, mid - 1)
            root.right = build(mid + 1, r)
            return root

        return build(0, len(nums) - 1)

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

本题也不难,在 求二叉搜索树的最小绝对差 和 众数 那两道题目 都讲过了 双指针法,思路是一样的。

代码随想录

视频讲解:普大喜奔!二叉树章节已全部更完啦!| LeetCode:538.把二叉搜索树转换为累加树_哔哩哔哩_bilibili

给你一棵 BST,让每个节点的新值 = 它本身的值 + 所有比它大的节点的值

      4
     / \
    1   6

中序序列是 [1,4,6]

  • 1 → 新值 = 1 + 4 + 6 = 11

  • 4 → 新值 = 4 + 6 = 10

  • 6 → 新值 = 6

核心思路:反向中序遍历

BST 的中序(Left → Node → Right)是递增序列。
所以我们只要 反向遍历(Right → Node → Left),就是从大到小访问。

👉 遍历时维护一个累计和 s

  • 一开始 s = 0

  • 每访问一个节点,就做:

    1. s += 节点原值

    2. 节点新值 = s

这样,s 就一直保存了“所有已经访问过的更大节点之和”。


步骤举例

以 BST:

       4
      / \
     1   6
        / \
       5   7

中序序列(升序): [1,4,5,6,7]
反向中序序列(降序): [7,6,5,4,1]

遍历过程

  1. 访问 7
    s = 0 + 7 = 7
    节点 7 → 新值 = 7

  2. 访问 6
    s = 7 + 6 = 13
    节点 6 → 新值 = 13

  3. 访问 5
    s = 13 + 5 = 18
    节点 5 → 新值 = 18

  4. 访问 4
    s = 18 + 4 = 22
    节点 4 → 新值 = 22

  5. 访问 1
    s = 22 + 1 = 23
    节点 1 → 新值 = 23

结果树

       22
      /  \
     23   13
         /  \
        18   7
解法一:递归反向中序(直观,推荐)
# 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':
        """
        反向中序(Right -> Node -> Left)遍历 BST:
        - 维护一个累计和 self.s,表示“已经处理过的更大节点值之和”
        - 降序访问,先到最右(最大),一路向左推进
        - 每到一个节点:先把它的值加到累计和,再用累计和覆盖该节点
        """
        self.s = 0  # 累计和(到当前为止,已访问过的更大或相等节点之和)

        def dfs(node: 'TreeNode'):
            if not node:
                return
            # 1) 先处理右子树(更大的值)
            dfs(node.right)

            # 2) 再处理当前节点:把当前值纳入累计和,并回填到节点
            self.s += node.val
            node.val = self.s

            # 3) 最后处理左子树(较小的值)
            dfs(node.left)

        dfs(root)
        return 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 convertBST(self, root: 'TreeNode') -> 'TreeNode':
        """
        迭代版反向中序:
        - 用显式栈模拟递归
        - 模板:一路向右入栈 -> 弹栈访问节点 -> 转向左子树
        """
        s = 0
        stack = []
        cur = root

        # 反向中序模板:优先向右深入
        while cur or stack:
            # 1) 沿右链入栈(降序中更靠前)
            while cur:
                stack.append(cur)
                cur = cur.right

            # 2) 弹出一个节点 = 访问该节点
            cur = stack.pop()

            # 用累计和更新当前节点
            s += cur.val
            cur.val = s

            # 3) 转向左子树
            cur = cur.left

        return root

总结篇

好了,二叉树大家就这样刷完了,做一个总结吧

代码随想录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清水泛轻舟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值