算法题 把二叉搜索树转换为累加树

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

问题描述

给定一个二叉搜索树(BST),将其转换为累加树(Greater Tree),使得每个节点的值变成原树中所有大于或等于该节点值的和。

示例

输入: BST
      5
    /   \
   2     13

输出: 累加树
      18
    /    \
   20     13

算法思路

逆中序遍历(右-根-左)

  1. 核心思想
    • 利用二叉搜索树的中序遍历为有序序列的特性
    • 逆中序遍历(从大到小)并累加节点值
  2. 遍历顺序
    • 先遍历右子树 → 处理当前节点 → 再遍历左子树
  3. 累加机制
    • 维护一个全局累加变量 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]

  1. 递归流程
    • 遍历右子树:节点13 → sum=0+13=13 → 节点值更新为13
    • 回溯到根节点:节点5 → sum=13+5=18 → 节点值更新为18
    • 遍历左子树:节点2 → sum=18+2=20 → 节点值更新为20
  2. 结果树
        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();
}

关键点

  1. 逆中序遍历顺序

    • 右子树 → 当前节点 → 左子树
    • 确保从大到小访问节点
  2. 累加机制

    • sum 变量记录遍历过程中的累加值
    • 节点新值 = 原值 + 当前累加值
    • 更新累加值:sum = 新节点值
  3. 二叉搜索树特性

    • 右子树值 > 根值 > 左子树值
    • 逆序遍历即从最大值到最小值

常见问题

  1. 为什么使用逆中序遍历?

    • 因为累加树要求每个节点值变为所有大于等于它的值之和,需要从大到小累加
  2. 如何处理空树?

    • 递归和迭代方法在入口处都兼容空树处理
  3. 能否用 Morris 遍历优化空间?

    • 可以,Morris 遍历可将空间复杂度优化到 O(1),但实现较复杂
  4. 递归和迭代哪个更优?

    • 递归更简洁,但存在栈溢出风险(深树)
    • 迭代空间效率更稳定,但代码稍复杂
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值