LeetCode LCR 054. 把二叉搜索树转换为累加树
题目描述



题意分析
把二叉搜索树中每个节点的值,改为原树中所有大于等于它的节点值之和。只修改数值,保留原来的节点和连接关系,最后返回原根节点。
二叉搜索树的有序性决定了访问顺序:从大到小处理节点时,当前需要的那些更大值都已经访问过,可以用一个累计和直接求出新值。
解法:反中序累加
核心思路
[!blue]
二叉搜索树按“左、根、右”访问得到升序,改成“右、根、左”就得到降序。访问当前节点之前,所有原值比它大的节点都已处理,而更小的节点尚未处理。
用共享变量
s保存已经访问的原节点值之和。到达当前节点时,先执行s += root.val,再写入root.val = s:加上当前原值之后,累计和恰好包含全部更大节点和当前节点,正是它的新值。写回后继续处理左子树,它需要的累计范围还包括刚处理的当前节点。虽然右侧节点已经改值,但它们的原值早已计入
s,后续不会再次读取这些节点参与累加,所以原地修改不会污染计算。
s的初值为 0,并在整次遍历中共享。Java 通过成员变量保存累计进度,Go 通过闭包保存;递归进入另一棵子树时,不能重新创建一个从 0 开始的累计和。节点值可以为负,累计和不一定递增。正确性依赖的是降序访问覆盖了哪些原节点,而不是累计数值的变化方向。
解题步骤
- 让累计和
s从 0 开始,并由整次遍历共享。- 递归遇到空节点直接返回,不改变累计和。
- 先处理右子树,再将当前原值加入
s,把s写回当前节点。- 最后处理左子树,继续使用已经更新的累计和。
- 遍历完成后返回原根;空树会直接原样返回。
代码实现
class Solution {
private int s;
public TreeNode convertBST(TreeNode root) {
dfs(root);
return root;
}
private void dfs(TreeNode root) {
if (root == null) {
return;
}
dfs(root.right);
s += root.val;
root.val = s;
dfs(root.left);
}
}
func convertBST(root *TreeNode) *TreeNode {
s := 0
var dfs func(*TreeNode)
dfs = func(root *TreeNode) {
if root == nil {
return
}
dfs(root.Right)
s += root.Val
root.Val = s
dfs(root.Left)
}
dfs(root)
return root
}
复杂度分析
设节点数为
n,树高为h。
- 时间复杂度:$O(n)$,每个节点只访问一次,进行一次累加与赋值。
- 空间复杂度:$O(h)$,来自递归栈;树退化成链时为 $O(n)$。
关键点总结
[!green]
- 反中序把更大值放在前面,让需要的总和变成一次顺序累加。
- 先使用节点原值,再写回累计和,已处理节点不再参与后续读取。
- 累计和贯穿整次遍历,不能按节点或子树重新开始计数。
易错点总结
[!yellow]
- 按普通中序累加:得到的是较小值的和,与题目方向相反。
- 先覆盖节点再累加:会丢失它的原值,导致后续累计错误。
- 在每层递归中重置累计和:会丢掉已经访问的更大节点信息。
- 假设累计和一定递增:负值节点可能使累计和减小,不能据此剪枝。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 230. 二叉搜索树中第 K 小的元素 | 中等 | 都利用BST中序有序性,本题改用逆中序累计更大值,原题按升序定位第k项。 |
| 897. 递增顺序搜索树 | 简单 | 同样通过有序遍历改造BST,原题改连接而保留节点值,本题改值并保留连接。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!