怎么把二叉搜索树转换为累加树?

187 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第29天,点击查看活动详情


大家好呀,我是帅蛋。

今天解决把二叉搜索树转换为累加树,这是一道看懂了题意难度洒洒水,看不懂难度拉满的二叉搜索树题。

538-0

下面让我们一起来搞一下这道题。

538-1

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

题意

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

示例

输入:[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]

538-2

提示

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

题目解析

这道题官方定义难度为中等,我感觉更多的可能是难在对“累加树”题意的理解上,理解了题意,这道题就成了一道水题。

538-3

题意中说“使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。”,其实翻译一下就是:对于每个节点,把那些大于它的节点值累加给它。

这么一看,是不是就很清楚啦。

然后这个问题就成了怎么找比当前节点大的节点值?简直就是送分题!

我们做了这么多的二叉搜索树,按理说看到这个问题的时候,我们脑袋里应该立马蹦出二叉搜索树的性质:

对二叉搜索树进行中序遍历时,得到的结果是一个有序的序列!

我们以下图为例:

538-4

中序遍历的顺序是左子树、根节点、右子树,其有序序列为 [0,1,2,3,4,5,6,7,8]。

这样看就很明了,从数组最右边开始,从右向左把前一个元素的值累加给自己。

这个顺序转换在二叉搜索树中,其实就是先找到二叉搜索树中最大的值,也就是整棵二叉搜索树右下角的值。

累加的顺序其实成了[8,7,6,5,4,3,2,1,0] 这样的顺序,其实就是二叉搜索树反着中序遍历来,即遍历的顺序为:右子树、根节点、左子树,同时按照这个顺序累加就行了。

你看,就是这么简单。

538-5

递归法

但凡是【递归算法】,我们还是按照两步来走:

(1) 找出重复的子问题。

中序遍历的顺序是:左子树、根、右子树。

ACM 选手带你玩转二叉树前中后序遍历(递归版)

对于左子树、右子树来说,也是同样的遍历顺序。

而我在上面说过,本题中是反着中序遍历来,是:右子树、根、左子树。

对于左子树和右子树也都是同样的顺序。

那这个重复的子问题就是:先遍历右子树、再对根节点进行累加、最后遍历左子树

# 遍历右子树
self.nodeSum(root.right)
# 对节点值累加
root.val += self.preSum
# 更新 preSum 值
self.preSum = root.val
# 遍历左子树
self.nodeSum(root.left)

(2) 确定终止条件。

和中序遍历相同,就是当前的节点为空,空的没啥好遍历的。

if root == None:
    return None

最重要的两点确定完了,然后代码基本上就出来。

Python 代码实现

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
​
    def __init__(self):
        # 记录前一个节点的累加值
        self.preSum = 0
​
    def nodeSum(self, root):
        if root == None:
            return None
        # 遍历右子树
        self.nodeSum(root.right)
        # 对节点值累加
        root.val += self.preSum
        # 更新 preSum 值
        self.preSum = root.val
        # 遍历左子树
        self.nodeSum(root.left)
​
    def convertBST(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
​
        self.nodeSum(root)
        return root

Java 代码实现

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    // 记录前一个节点的累加值
    int preSum;
    public void nodeSum(TreeNode root){
        if(root == null){
            return ;
        }
        // 遍历右子树
        nodeSum(root.right);
        // 对节点值累加
        root.val += preSum;
        // 更新 preSum 值
        preSum = root.val;
        // 遍历左子树
        nodeSum(root.left);
    }
​
    public TreeNode convertBST(TreeNode root) {
        preSum = 0;
        nodeSum(root);
        return root;
    }
}

本题解,每个节点都要访问一次,所以时间复杂度为 O(n)

在递归过程中调用了额外的栈空间,栈的大小取决于二叉搜索树的高度,同样二叉搜索树最坏情况下的高度为 n,所以空间复杂度为 O(n)

非递归法(迭代)

非递归法,其实就是中序遍历的非递归法,还是那句话,在本题中是逆着中序遍历,顺序是右子树、操作节点、左子树。

ACM 选手带你玩转二叉树前中后序遍历(非递归版)

我们以下图为例:

538-6

首先初始化栈 stack、记录前一个节点的累加值 preSum、记录当前节点的 cur。

538-7

# 初始化栈
stack = []
# 记录前一个节点的累加值
preSum = 0
cur = root

第 1 步,从根节点开始,一直向右子树遍历,同时将当前的节点压入栈中。

538-8

# 一直向右子树走,每一次将当前节点保存在栈中
if cur:
    stack.append(cur)
    cur = cur.right

第 2 步,此时 cur 走到了最右边,弹出栈顶元素,此时 cur.val = 8,preSum = 0,所以 cur.val = cur.val + preSum = 8 + 0 = 8。

538-9

cur = stack.pop()
cur.val += preSum

之后令 preSum = cur.val = 8,cur = cur.left = null。

538-10

preSum = cur.val
cur = cur.left

第 3 步,继续 while 循环,当前 cur 为空,继续弹出栈顶元素。

此时 cur.val = 7,preSum = 8,所以 cur.val = cur.val + preSum = 7 + 8 = 15。

538-11

之后令 preSum = cur.val = 15,cur = cur.left = null。

538-12

同样,接下来的操作步骤都是按照上面的步骤,不断地出栈、入栈,更新节点值和 preSum 的值,直至栈为空。

我在这就不全部画出来了,大家自己动手尝试着把下面的步骤实现出来。

538-13

Python 代码实现

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def convertBST(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if root == None:
            return None
        # 初始化栈
        stack = []
        # 记录前一个节点的累加值
        preSum = 0
        cur = root
​
        while cur or stack:
            # 一直向右子树走,每一次将当前节点保存在栈中
            if cur:
                stack.append(cur)
                cur = cur.right
            # 当前节点为空,证明走到了最右边,从栈中弹出节点进行累加操作
            # 开始对左子树重复上述过程
            else:
                cur = stack.pop()
                cur.val += preSum
                preSum = cur.val
                cur = cur.left
        
        return root

Java 代码实现

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public TreeNode convertBST(TreeNode root) {
        if(root == null){
            return null;
        }
        // 初始化栈
        Stack<TreeNode> stack = new Stack<TreeNode>();
        // 记录前一个节点的累加值
        int preSum = 0;
        TreeNode cur = root;
​
        while(cur != null || stack.size() > 0){
            // 一直向右子树走,每一次将当前节点保存在栈中
            if(cur != null){
                stack.add(cur);
                cur = cur.right;
            }
            // 当前节点为空,证明走到了最右边,从栈中弹出节点进行累加操作
            // 开始对左子树重复上述过程
            else{
                cur = stack.pop();
                cur.val += preSum;
                preSum = cur.val;
                cur = cur.left;
            }
        }
        return root;
    }
}

和递归法一样,非递归法的时间复杂度为 O(n)空间复杂度为 O(n)。


图解把二叉搜索树转换为累加树到这就结束辣,至此二叉搜索树的实战题目暂且告一段落。

说起来还有点不舍得,还想再多搞几道,毕竟我太爱二叉搜索树了~

538-14

下阶段我们将开始平衡二叉树的学习,快把期待打在留言区!

我是帅蛋,我们平衡二叉树见~