携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第26天,点击查看活动详情
大家好呀,我是帅蛋。
今天解决把二叉搜索树转换为累加树,这是一道看懂了题意难度洒洒水,看不懂难度拉满的二叉搜索树题。
下面让我们一起来搞一下这道题。
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]
提示
- 树中的节点数介于 0 和 10^4 之间。
- 每个节点的值介于 -10^4 和 10^4 之间。
- 树中的所有值互不相同。
- 给定的树为二叉搜索树。
题目解析
这道题官方定义难度为中等,我感觉更多的可能是难在对“累加树”题意的理解上,理解了题意,这道题就成了一道水题。
题意中说“使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。”,其实翻译一下就是:对于每个节点,把那些大于它的节点值累加给它。
这么一看,是不是就很清楚啦。
然后这个问题就成了怎么找比当前节点大的节点值?简直就是送分题!
我们做了这么多的二叉搜索树,按理说看到这个问题的时候,我们脑袋里应该立马蹦出二叉搜索树的性质:
对二叉搜索树进行中序遍历时,得到的结果是一个有序的序列!
我们以下图为例:
中序遍历的顺序是左子树、根节点、右子树,其有序序列为 [0,1,2,3,4,5,6,7,8]。
这样看就很明了,从数组最右边开始,从右向左把前一个元素的值累加给自己。
这个顺序转换在二叉搜索树中,其实就是先找到二叉搜索树中最大的值,也就是整棵二叉搜索树右下角的值。
累加的顺序其实成了[8,7,6,5,4,3,2,1,0] 这样的顺序,其实就是二叉搜索树反着中序遍历来,即遍历的顺序为:右子树、根节点、左子树,同时按照这个顺序累加就行了。
你看,就是这么简单。
递归法
但凡是【递归算法】,我们还是按照两步来走:
(1) 找出重复的子问题。
中序遍历的顺序是:左子树、根、右子树。
对于左子树、右子树来说,也是同样的遍历顺序。
而我在上面说过,本题中是反着中序遍历来,是:右子树、根、左子树。
对于左子树和右子树也都是同样的顺序。
那这个重复的子问题就是:先遍历右子树、再对根节点进行累加、最后遍历左子树。
# 遍历右子树
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) 。
非递归法(迭代)
非递归法,其实就是中序遍历的非递归法,还是那句话,在本题中是逆着中序遍历,顺序是右子树、操作节点、左子树。
我们以下图为例:
首先初始化栈 stack、记录前一个节点的累加值 preSum、记录当前节点的 cur。
# 初始化栈
stack = []
# 记录前一个节点的累加值
preSum = 0
cur = root
第 1 步,从根节点开始,一直向右子树遍历,同时将当前的节点压入栈中。
# 一直向右子树走,每一次将当前节点保存在栈中
if cur:
stack.append(cur)
cur = cur.right
第 2 步,此时 cur 走到了最右边,弹出栈顶元素,此时 cur.val = 8,preSum = 0,所以 cur.val = cur.val + preSum = 8 + 0 = 8。
cur = stack.pop()
cur.val += preSum
之后令 preSum = cur.val = 8,cur = cur.left = null。
preSum = cur.val
cur = cur.left
第 3 步,继续 while 循环,当前 cur 为空,继续弹出栈顶元素。
此时 cur.val = 7,preSum = 8,所以 cur.val = cur.val + preSum = 7 + 8 = 15。
之后令 preSum = cur.val = 15,cur = cur.left = null。
同样,接下来的操作步骤都是按照上面的步骤,不断地出栈、入栈,更新节点值和 preSum 的值,直至栈为空。
我在这就不全部画出来了,大家自己动手尝试着把下面的步骤实现出来。
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)。
图解把二叉搜索树转换为累加树到这就结束辣,至此二叉搜索树的实战题目暂且告一段落。
说起来还有点不舍得,还想再多搞几道,毕竟我太爱二叉搜索树了~
下阶段我们将开始平衡二叉树的学习,快把期待打在留言区!
我是帅蛋,我们平衡二叉树见~