538.把二叉搜索树转换为累加树
问题:给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 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]
输入:root = [0,null,1]
输出:[1,null,1]
输入:root = [1,0,2]
输出:[3,3,2]
输入:root = [3,2,4,1]
输出:[7,9,4,10]
思路:
- 中序遍历
二叉搜索树,中序遍历为升序序列,那么逆中序遍历,即按右子树、根节点、左子树遍历,则是降序序列。我们要使得每个节点 node
的新值等于原树中大于或等于 node.val
的值之和(前面说了,中序遍历为升序)。也就是说,该新树中,该节点的值等于自己的值+中序遍历序列该位置右边的所有节点值
,有些类似于前缀和,我们将这个升序序列变成降序序列,然后再求各个节点对应的前缀和就行了。
class Solution {
private int sum;
public TreeNode convertBST(TreeNode root) {
if(root == null) return null;
sum = 0;
med(root);
return root;
}
private void med(TreeNode node){
if(node == null) {
return;
}
med(node.right);
sum += node.val;
node.val = sum;
med(node.left);
}
}
//不使用全局变量的反中序遍历
class Solution {
public TreeNode convertBST(TreeNode root) {
med(root, 0);
return root;
}
private int med(TreeNode node, int rightSum){
if(node == null) {
return rightSum;
}
node.val += med(node.right, rightSum);
return med(node.left, node.val);
}
}
- Morrirs中序遍历
模板如下:
递归和使用栈的树的遍历实现的算法的空间复杂度都是O(h).
实现:
记当前节点为cur
- 如果cur无左孩子,cur向右移动(cur = cur.right)
- 如果cur有左孩子,找到cur左子树上最右叶子节点,记为mostRight
- 如果mostRight的右孩子为空,让其指向cur节点,cur向左移动(cur = cur.left)
- 如果mostRight的右孩子指向cur(当前节点),让其指向空,cur向右移动(cur = cur.right)
实质:建立一种机制,对于没有左子树的节点只到达一次,对于有左子树的节点会到达两次。
有左子树的节点,第一次到达是为了寻找前驱节点,使其右孩子指向当前节点,第二次到达因为遍历到了当前节点左子树的最右叶子节点,需要遍历当前节点的右子树,需要将指针重新指向当前节点。
public static void morrisPre(Node head) {
if(head == null){
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null){
// cur表示当前节点,mostRight表示cur的左孩子的最右节点
mostRight = cur.left;
if(mostRight != null){
// cur有左孩子,找到cur左子树最右节点
while (mostRight.right !=null && mostRight.right != cur){
mostRight = mostRight.right;
}
// mostRight的右孩子指向空,让其指向cur,cur向左移动
if(mostRight.right == null){
mostRight.right = cur;
//当前节点左子树的最右节点的右子树为空
//说明当前节点未被遍历,此时为第一次遇到该节点,
//为前序遍历的位置之一
//前序遍历
cur = cur.left;
continue;
}else {
// mostRight的右孩子指向cur,让其指向空,cur向右移动
mostRight.right = null;
//当前节点左子树最右节点的右孩子指向当前节点
//说明是第二次遇到,当前节点的左子树已经遍历完了
//为中序遍历的位置之一
//中序遍历
}
}else {
//此时对应当前节点的左子树为空
//也是第一次遇到该节点,为前序和中序遍历的位置之一
//前序遍历
//中序遍历
}
//中序遍历也可以写在这里,只需要在这遍历一次
cur = cur.right;
}
}
这个是正序的。需要稍微改造一下
/**
* 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;
}
TreeNode cur = root;
TreeNode mostleft = null;
int rightSum = 0;
while(cur != null){
// System.out.println(cur.val);
mostleft = cur.right;
if(mostleft != null){
//寻找当前节点右子树的最左节点
while(mostleft.left != null && mostleft.left != cur){
mostleft = mostleft.left;
}
//找到最左节点,将最左节点的 左子树 作为当前节点的前驱节点,继续向右
//注意,此时 mostleft 指向的是当前节点右子树中的最左节点,需要判断的是该最左节点的 左孩子是否为空
//第一次写的时候,这块写成了most == null,找了半天才找到问题
if(mostleft.left == null){
mostleft.left = cur;
cur = cur.right;
continue;
} else {
//当前节点的右子树已经遍历完了,将树恢复原状
mostleft.left = null;
}
}
//走到这有两种情况:1.当前节点的右子树为空, 2.当前节点的右子树已经遍历完了
//中序遍历位置
rightSum += cur.val;
cur.val = rightSum;
cur = cur.left;
}
return root;
}
}
整理思路,记录博客,以便复习。若有误,望指正~