leetcode
leetcode 451 ~ 500
把二叉搜索树转换为累加树

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

难度:

标签:

题目描述

给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。

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

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

注意:本题和 1038: https://leetcode-cn.com/problems/binary-search-tree-to-greater-sum-tree/ 相同

 

示例 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]

示例 3:

输入:root = [1,0,2]
输出:[3,3,2]

示例 4:

输入:root = [3,2,4,1]
输出:[7,9,4,10]

 

提示:

  • 树中的节点数介于 0 和 104 之间。
  • 每个节点的值介于 -104 和 104 之间。
  • 树中的所有值 互不相同
  • 给定的树为二叉搜索树。

代码结果

运行时间: 64 ms, 内存: 17.4 MB


// Java Stream不直接支持树的遍历和操作,这里依然使用递归方式实现。
// 1. 通过反向中序遍历更新每个节点的值。
// 2. 使用lambda表达式和Optional类提高代码简洁性。
 
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}
 
import java.util.Optional;
 
public class Solution {
    private int sum = 0;
    
    public TreeNode convertBST(TreeNode root) {
        Optional.ofNullable(root).ifPresent(node -> {
            convertBST(node.right);
            sum += node.val;
            node.val = sum;
            convertBST(node.left);
        });
        return root;
    }
}

解释

方法:

这道题的解法使用了反向中序遍历的思路。在二叉搜索树中,中序遍历可以得到一个递增的序列。而本题要求每个节点的新值等于原树中大于等于该节点值的所有节点值之和,实际上就是反向中序遍历累加的过程。具体来说,我们先遍历右子树,然后处理当前节点,最后遍历左子树。在处理当前节点时,我们将当前的累加和加到该节点的值上,然后更新累加和。这样,在遍历完整棵树后,每个节点的值就变成了大于等于该节点的所有节点值之和。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么选择使用反向中序遍历而不是其他遍历方法来实现累加树?
在二叉搜索树中,中序遍历可以按照递增顺序访问所有节点。对于本题,我们需要计算大于等于每个节点的所有节点值之和。使用反向中序遍历(即先访问右子树,然后访问根节点,最后访问左子树)可以实现从最大值到最小值的顺序访问节点。这样,我们可以持续累加当前及之前访问的节点的值,直接更新每个节点,而无需额外的数据结构来存储中间结果,因此更加高效和直观。
🦆
在反向中序遍历中,加到节点值上的累加和`s`是如何更新的,具体的更新逻辑是什么?
在反向中序遍历中,累加和`s`是用来记录当前节点以及所有已访问的右侧节点的值的总和。当访问到一个节点时,将`s`的当前值加到该节点的值上,然后将更新后的节点值重新赋值给`s`。这样,`s`始终保持为当前节点以及所有右侧节点的值的总和,为之后的节点提供正确的累加基数。
🦆
在实际代码实现中,`dfs`函数的终止条件是什么?如何确保不会对空节点进行操作?
在`dfs`函数中,终止条件是当遍历到的节点为`None`时,函数直接返回并不执行任何操作。这一条件检查发生在函数的开始,确保在尝试访问或修改节点属性(如值或子节点)之前,节点是存在的。这样的检查避免了对空节点的任何操作,保证了代码的安全性和正确性。
🦆
在处理完当前节点后,为什么要立即更新累加和`s`,而不是在遍历完左子树或右子树后更新?
在处理完当前节点时立即更新累加和`s`是因为`s`需要在访问左子树之前包含当前节点的值。由于我们是按照“右子树 - 根节点 - 左子树”的顺序进行遍历,当前节点的值在加入累加和后,应立即用于更新左子树中的节点。如果延迟更新`s`,左子树中的节点将无法正确地累加包含当前节点的值,从而导致错误的结果。

相关问题