LeetCode 538. 把二叉搜索树转换为累加树
问题描述
给定一个二叉搜索树(BST),将其转换为累加树(Greater Tree),使得每个节点的值变成原树中所有大于或等于该节点值的和。
示例:
输入: BST
5
/ \
2 13
输出: 累加树
18
/ \
20 13
算法思路
逆中序遍历(右-根-左)
:
- 核心思想:
- 利用二叉搜索树的中序遍历为有序序列的特性
- 逆中序遍历(从大到小)并累加节点值
- 遍历顺序:
- 先遍历右子树 → 处理当前节点 → 再遍历左子树
- 累加机制:
- 维护一个全局累加变量
sum
- 每个节点的新值 = 原值 + 当前累加值
- 更新累加值为新节点值
- 维护一个全局累加变量
代码实现
方法一:递归(DFS)
class Solution {
private int sum = 0; // 全局累加变量
public TreeNode convertBST(TreeNode root) {
// 递归处理
dfs(root);
return root;
}
/**
* 逆中序遍历(右-根-左)
*
* @param node 当前节点
*/
private void dfs(TreeNode node) {
if (node == null) return;
// 1. 先遍历右子树(较大值部分)
dfs(node.right);
// 2. 处理当前节点
sum += node.val; // 累加当前节点值
node.val = sum; // 更新节点值为累加和
// 3. 再遍历左子树(较小值部分)
dfs(node.left);
}
}
方法二:迭代(栈)
class Solution {
public TreeNode convertBST(TreeNode root) {
int sum = 0;
TreeNode cur = root;
Deque<TreeNode> stack = new ArrayDeque<>();
// 迭代逆中序遍历
while (cur != null || !stack.isEmpty()) {
// 1. 将当前节点及其右子树入栈
while (cur != null) {
stack.push(cur);
cur = cur.right; // 优先处理右子树
}
// 2. 弹出栈顶节点处理
cur = stack.pop();
sum += cur.val; // 累加节点值
cur.val = sum; // 更新节点值为累加和
// 3. 转向左子树
cur = cur.left;
}
return root;
}
}
算法分析
- 时间复杂度:O(n)
- 两种方法都遍历每个节点一次
- 空间复杂度:
- 递归:O(h),h为树高度(递归栈空间)
- 迭代:O(n),最坏情况下栈空间存储所有节点
算法过程
BST: [5,2,13]
:
- 递归流程:
- 遍历右子树:节点13 →
sum=0+13=13
→ 节点值更新为13 - 回溯到根节点:节点5 →
sum=13+5=18
→ 节点值更新为18 - 遍历左子树:节点2 →
sum=18+2=20
→ 节点值更新为20
- 遍历右子树:节点13 →
- 结果树:
18 / \ 20 13
测试用例
public static void main(String[] args) {
Solution solution = new Solution();
// 测试用例1:标准示例
TreeNode root1 = new TreeNode(5);
root1.left = new TreeNode(2);
root1.right = new TreeNode(13);
TreeNode result1 = solution.convertBST(root1);
printTree(result1); // 预期:根=18, 左=20, 右=13
// 测试用例2:左斜树
TreeNode root2 = new TreeNode(3);
root2.left = new TreeNode(2);
root2.left.left = new TreeNode(1);
TreeNode result2 = solution.convertBST(root2);
printTree(result2); // 预期:根=3->6, 左=2->5, 左左=1->6
// 测试用例3:右斜树
TreeNode root3 = new TreeNode(1);
root3.right = new TreeNode(2);
root3.right.right = new TreeNode(3);
TreeNode result3 = solution.convertBST(root3);
printTree(result3); // 预期:根=1->6, 右=2->5, 右右=3->3
// 测试用例4:空树
TreeNode root4 = null;
TreeNode result4 = solution.convertBST(root4);
printTree(result4); // 预期:null
// 测试用例5:单节点
TreeNode root5 = new TreeNode(10);
TreeNode result5 = solution.convertBST(root5);
printTree(result5); // 预期:10->10
}
// 辅助方法:中序遍历打印树
private static void printTree(TreeNode root) {
if (root == null) {
System.out.println("null");
return;
}
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
System.out.print(cur.val + " ");
cur = cur.right;
}
System.out.println();
}
关键点
-
逆中序遍历顺序:
- 右子树 → 当前节点 → 左子树
- 确保从大到小访问节点
-
累加机制:
sum
变量记录遍历过程中的累加值- 节点新值 = 原值 + 当前累加值
- 更新累加值:
sum = 新节点值
-
二叉搜索树特性:
- 右子树值 > 根值 > 左子树值
- 逆序遍历即从最大值到最小值
常见问题
-
为什么使用逆中序遍历?
- 因为累加树要求每个节点值变为所有大于等于它的值之和,需要从大到小累加
-
如何处理空树?
- 递归和迭代方法在入口处都兼容空树处理
-
能否用 Morris 遍历优化空间?
- 可以,Morris 遍历可将空间复杂度优化到 O(1),但实现较复杂
-
递归和迭代哪个更优?
- 递归更简洁,但存在栈溢出风险(深树)
- 迭代空间效率更稳定,但代码稍复杂