leetcode
leetcode 951 ~ 1000
从二叉搜索树到更大和树

从二叉搜索树到更大和树

难度:

标签:

题目描述

给定一个二叉搜索树 root (BST),请将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。

提醒一下, 二叉搜索树 满足下列约束条件:

  • 节点的左子树仅包含键 小于 节点键的节点。
  • 节点的右子树仅包含键 大于 节点键的节点。
  • 左右子树也必须是二叉搜索树。

 

示例 1:

输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]
输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]

示例 2:

输入:root = [0,null,1]
输出:[1,null,1]

 

提示:

  • 树中的节点数在 [1, 100] 范围内。
  • 0 <= Node.val <= 100
  • 树中的所有值均 不重复 。

 

注意:该题目与 538: https://leetcode-cn.com/problems/convert-bst-to-greater-tree/  相同

代码结果

运行时间: 23 ms, 内存: 15.9 MB


/*
题目思路:
- 使用Stream API的方式来实现相同的逻辑。
- 由于Stream API主要用于操作集合类,通常不会直接处理树结构,因此,这里提供了一种模拟Stream API风格的代码。
*/

import java.util.ArrayList;
import java.util.List;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

public class Solution {
    private int sum = 0;
    
    public TreeNode convertBST(TreeNode root) {
        List<TreeNode> list = new ArrayList<>();
        traverseAndCollect(root, list);
        list.stream().sorted((a, b) -> b.val - a.val).forEach(node -> {
            sum += node.val;
            node.val = sum;
        });
        return root;
    }
    
    private void traverseAndCollect(TreeNode node, List<TreeNode> list) {
        if (node == null) return;
        traverseAndCollect(node.left, list);
        list.add(node);
        traverseAndCollect(node.right, list);
    }
}

解释

方法:

本题的解决方案基于二叉搜索树(BST)的特性,利用中序遍历的变体来解决问题。传统的中序遍历(左-中-右)可以访问BST中的节点,使节点值按升序排列。为了计算每个节点的新值,需要对所有大于或等于该节点值的节点值进行求和,可以通过反中序遍历(右-中-左)来实现,这样可以从最大的节点开始累加值。具体过程是:从BST的最右侧节点开始遍历,累计过程中将每个节点的值加到一个累积和中,并更新当前节点值为这个累积和。这种方式确保了每个节点在更新其值之前,所有大于该节点的值已被累加到总和中。

时间复杂度:

O(n)

空间复杂度:

O(h),其中 h 是树的高度

代码细节讲解

🦆
在你的解决方案中,为什么选择反中序遍历(右-中-左)而不是正中序遍历来更新节点值?
在本题中,每个节点需要更新为原始节点值加上所有大于该节点的节点值之和。使用反中序遍历(右-中-左)可以确保在访问一个节点之前,已经访问了所有的大于该节点的值。这样,当访问到一个节点时,就可以直接使用之前累加的和(所有访问过的节点的值之和),直接更新当前节点值。如果使用正中序遍历(左-中-右),则需要在访问一个节点后,再去累加所有大于该节点的值,这会复杂和低效,因为还需要存储或重新计算大于当前节点的节点值之和。
🦆
你提到了反中序遍历可以从最大的节点开始累加值,这个过程是否假设了树的结构总是对称或平衡?如果不是,这种方法的效率如何受到影响?
该方法并不假设树的结构必须是对称或平衡的。反中序遍历简单地遵循右-中-左的顺序,这保证了无论树的结构如何,总是先访问较大的值。树的结构(如是否平衡)主要影响的是遍历的效率。在极端情况下(如退化成链表的情况),遍历可能导致较高的递归深度,从而影响效率。然而,对于二叉搜索树的修改操作,无论树的形状如何,时间复杂度均为 O(n),因为每个节点仍需要访问一次。
🦆
在代码中,`self.pre`变量是如何确保在递归调用中正确累加的?是否存在线程安全问题或者递归深度过深导致的堆栈溢出风险?
`self.pre`变量用作全局累加和的存储,其在递归过程中被持续更新,确保每次递归调用时都能够访问到最新的累加值。由于Python的递归调用是基于单线程执行的,不存在线程安全问题。然而,对于非常深的二叉树,递归调用可能导致堆栈溢出。这是递归实现的一个通常风险,可以通过增加递归限制或转换为迭代方式(使用显式堆栈)来解决。
🦆
示例输出中,树节点的更新值是如何确保包含了所有大于等于该节点值的节点之和?是否有可能存在漏算的情况?
在反中序遍历过程中,因为是从最大的节点开始,逐步累加至最小节点,确保了在更新当前节点值时,所有大于等于该节点的值已经被包括在内。由于每个节点在更新前,其所有大于它的节点已经被访问并累加,因此不会存在漏算的情况。这种方法通过正确的遍历顺序和累加保证了每个节点的新值计算的完整性和正确性。

相关问题