【算法学习】剑指 Offer II 054. 所有大于等于节点的值之和|538|1038(java / c / c++ / python / go / rust)

简介: 给定一个二叉搜索树,请将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。提醒一下,二叉搜索树满足下列约束条件: 节点的左子树仅包含键 小于 节点键的节点。 节点的右子树仅包含键 大于 节点键的节点。 左右子树也必须是二叉搜索树。

剑指 Offer II 054. 所有大于等于节点的值之和|538. 把二叉搜索树转换为累加树|1038. 把二叉搜索树转换为累加树:

给定一个二叉搜索树,请将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。

提醒一下,二叉搜索树满足下列约束条件:

  • 节点的左子树仅包含键 小于 节点键的节点。
  • 节点的右子树仅包含键 大于 节点键的节点。
  • 左右子树也必须是二叉搜索树。

样例 1

在这里插入图片描述

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

样例 2

输入:
    root = [0,null,1]
    
输出:
    [1,null,1]

样例 3

输入:
    root = [1,0,2]
    
输出:
    [3,3,2]

样例 4

输入:
    root = [3,2,4,1]
    
输出:
    [7,9,4,10]

提示

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

分析

  • 这道算法题需要翻译一下,通俗易懂一点。
  • 二叉树,大家应该比较熟悉,一般就是正序遍历,中序遍历,后序遍历,然后遍历的过程中进行一些简单的逻辑。
  • 二叉搜索树是一种特殊的二叉树,题目已经做了解释。
  • 由于要把每个节点的值替换成树中大于或者等于该节点值的所有节点值之和,根据二叉搜索树的特点,其实就是将每个节点的值替换成自己的值累加右子树的值之和。
  • 首先这一样需要遍历每个节点,但是按照什么顺序是关键,你会发现常规正序遍历,中序遍历,后序遍历都不顺畅。
  • 按照题意来看,显然最好的顺序就是先遍历右子树,然后根节点,接着左子树这样是最顺畅的,仅需要一个变量不断累加节点值,遍历到哪个节点就累加哪个节点的值,然后赋值给这个节点,这正好和中序遍历是反正的,也就是逆中序遍历。
  • 遍历这种套娃结构,一般就是递归或者循环(利用栈数据结构),递归相对比较简单,但是受调用栈限制,循环其实就是用栈这样的数据结构模拟了调用栈,本质上差不多,但是调用次数可以更多,因为调用栈的空间一般比较有限,堆空间比起来要大的多,但是实现同样的功能,逻辑上也复杂一些。
  • 无论用递归还是用栈数据结构去做循环,都需要与树结构对应的额外内存空间,有一种非常巧妙的遍历方法叫做 Morris遍历 ,可以将非递归遍历中的空间复杂度降为O (1),具体的实现二当家的已经加了详细注释,大家也可以去百科一下,做深入了解。

题解

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) {
        int      sum = 0;
        TreeNode cur = root;

        while (cur != null) {
            if (cur.right == null) {
                // 没有比自己值大的孩子
                // 轮到处理自己
                sum += cur.val;
                cur.val = sum;
                // 自己也遍历完了,所以该遍历比自己小的
                cur = cur.left;
            } else {
                // 右孩子的最左边,比自己大的最小的孩子
                // 也就是自己前边最后一个遍历的节点,下一个就该是自己
                TreeNode leftmostOfRight = cur.right;
                while (leftmostOfRight.left != null
                    && leftmostOfRight.left != cur) {
                    leftmostOfRight = leftmostOfRight.left;
                }
                if (leftmostOfRight.left == null) {
                    // 没有做过关联,说明第一次到这里,关联当前节点,保证遍历完比自己大的最小节点之后可以轮到自己
                    leftmostOfRight.left = cur;
                    cur = cur.right;
                } else {
                    // 第二次遍历到这里,说明比自己大的都遍历完了
                    // 解除关系,还原树结构
                    leftmostOfRight.left = null;
                    // 轮到处理自己
                    sum += cur.val;
                    cur.val = sum;
                    // 自己也遍历完了,所以该遍历比自己小的
                    cur = cur.left;
                }
            }
        }

        return root;
    }
}

c

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */


struct TreeNode* convertBST(struct TreeNode* root){
    int sum = 0;
    struct TreeNode *cur = root;

    while (cur != NULL) {
        if (cur->right == NULL) {
            // 没有比自己值大的孩子
            // 轮到处理自己
            sum += cur->val;
            cur->val = sum;
            // 自己也遍历完了,所以该遍历比自己小的
            cur = cur->left;
        } else {
            // 右孩子的最左边,比自己大的最小的孩子
            // 也就是自己前边最后一个遍历的节点,下一个就该是自己
            struct TreeNode *leftmostOfRight = cur->right;
            while (leftmostOfRight->left != NULL
                   && leftmostOfRight->left != cur) {
                leftmostOfRight = leftmostOfRight->left;
            }
            if (leftmostOfRight->left == NULL) {
                // 没有做过关联,说明第一次到这里,关联当前节点,保证遍历完比自己大的最小节点之后可以轮到自己
                leftmostOfRight->left = cur;
                cur = cur->right;
            } else {
                // 第二次遍历到这里,说明比自己大的都遍历完了
                // 解除关系,还原树结构
                leftmostOfRight->left = NULL;
                // 轮到处理自己
                sum += cur->val;
                cur->val = sum;
                // 自己也遍历完了,所以该遍历比自己小的
                cur = cur->left;
            }
        }
    }

    return root;
}

c++

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* convertBST(TreeNode* root) {
        int sum = 0;
        TreeNode *cur = root;

        while (cur != nullptr) {
            if (cur->right == nullptr) {
                // 没有比自己值大的孩子
                // 轮到处理自己
                sum += cur->val;
                cur->val = sum;
                // 自己也遍历完了,所以该遍历比自己小的
                cur = cur->left;
            } else {
                // 右孩子的最左边,比自己大的最小的孩子
                // 也就是自己前边最后一个遍历的节点,下一个就该是自己
                TreeNode *leftmostOfRight = cur->right;
                while (leftmostOfRight->left != nullptr
                       && leftmostOfRight->left != cur) {
                    leftmostOfRight = leftmostOfRight->left;
                }
                if (leftmostOfRight->left == nullptr) {
                    // 没有做过关联,说明第一次到这里,关联当前节点,保证遍历完比自己大的最小节点之后可以轮到自己
                    leftmostOfRight->left = cur;
                    cur = cur->right;
                } else {
                    // 第二次遍历到这里,说明比自己大的都遍历完了
                    // 解除关系,还原树结构
                    leftmostOfRight->left = nullptr;
                    // 轮到处理自己
                    sum += cur->val;
                    cur->val = sum;
                    // 自己也遍历完了,所以该遍历比自己小的
                    cur = cur->left;
                }
            }
        }

        return root;
    }
};

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: TreeNode) -> TreeNode:
        total = 0
        cur = root

        while cur:
            if not cur.right:
                # 没有比自己值大的孩子
                # 轮到处理自己
                total += cur.val
                cur.val = total
                # 自己也遍历完了,所以该遍历比自己小的
                cur = cur.left
            else:
                # 右孩子的最左边,比自己大的最小的孩子
                # 也就是自己前边最后一个遍历的节点,下一个就该是自己
                leftmostOfRight = cur.right
                while leftmostOfRight.left and leftmostOfRight.left != cur:
                    leftmostOfRight = leftmostOfRight.left

                if not leftmostOfRight.left:
                    # 没有做过关联,说明第一次到这里,关联当前节点,保证遍历完比自己大的最小节点之后可以轮到自己
                    leftmostOfRight.left = cur
                    cur = cur.right
                else:
                    # 第二次遍历到这里,说明比自己大的都遍历完了
                    # 解除关系,还原树结构
                    leftmostOfRight.left = None
                    # 轮到处理自己
                    total += cur.val
                    cur.val = total
                    # 自己也遍历完了,所以该遍历比自己小的
                    cur = cur.left

        return root
        

go

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func convertBST(root *TreeNode) *TreeNode {
    sum := 0
    cur := root

    for cur != nil {
        if cur.Right == nil {
            // 没有比自己值大的孩子
            // 轮到处理自己
            sum += cur.Val
            cur.Val = sum
            // 自己也遍历完了,所以该遍历比自己小的
            cur = cur.Left
        } else {
            // 右孩子的最左边,比自己大的最小的孩子
            // 也就是自己前边最后一个遍历的节点,下一个就该是自己
            leftmostOfRight := cur.Right
            for leftmostOfRight.Left != nil && leftmostOfRight.Left != cur {
                leftmostOfRight = leftmostOfRight.Left
            }
            if leftmostOfRight.Left == nil {
                // 没有做过关联,说明第一次到这里,关联当前节点,保证遍历完比自己大的最小节点之后可以轮到自己
                leftmostOfRight.Left = cur
                cur = cur.Right
            } else {
                // 第二次遍历到这里,说明比自己大的都遍历完了
                // 解除关系,还原树结构
                leftmostOfRight.Left = nil
                // 轮到处理自己
                sum += cur.Val
                cur.Val = sum
                // 自己也遍历完了,所以该遍历比自己小的
                cur = cur.Left
            }
        }
    }

    return root
}

rust

// Definition for a binary tree node.
// #[derive(Debug, PartialEq, Eq)]
// pub struct TreeNode {
//   pub val: i32,
//   pub left: Option<Rc<RefCell<TreeNode>>>,
//   pub right: Option<Rc<RefCell<TreeNode>>>,
// }
//
// impl TreeNode {
//   #[inline]
//   pub fn new(val: i32) -> Self {
//     TreeNode {
//       val,
//       left: None,
//       right: None
//     }
//   }
// }
use std::rc::Rc;
use std::cell::RefCell;
impl Solution {
    pub fn convert_bst(root: Option<Rc<RefCell<TreeNode>>>) -> Option<Rc<RefCell<TreeNode>>> {
        let mut sum = 0;
        let mut cur = root.clone();

        while cur.is_some() {
            if cur.as_ref().unwrap().borrow().right.is_none() {
                // 没有比自己值大的孩子
                // 轮到处理自己
                sum += cur.as_ref().unwrap().borrow().val;
                cur.as_ref().unwrap().borrow_mut().val = sum;
                // 自己也遍历完了,所以该遍历比自己小的
                cur = cur.unwrap().borrow().left.clone();
            } else {
                // 右孩子的最左边,比自己大的最小的孩子
                // 也就是自己前边最后一个遍历的节点,下一个就该是自己
                let mut leftmostOfRight = cur.as_ref().unwrap().borrow().right.clone();
                while leftmostOfRight.as_ref().unwrap().borrow().left.is_some()
                    && leftmostOfRight.as_ref().unwrap().borrow().left != cur {
                    leftmostOfRight = leftmostOfRight.unwrap().borrow().left.clone();
                }
                if leftmostOfRight.as_ref().unwrap().borrow().left.is_none() {
                    // 没有做过关联,说明第一次到这里,关联当前节点,保证遍历完比自己大的最小节点之后可以轮到自己
                    leftmostOfRight.unwrap().borrow_mut().left = cur.clone();
                    cur = cur.unwrap().borrow().right.clone();
                } else {
                    // 第二次遍历到这里,说明比自己大的都遍历完了
                    // 解除关系,还原树结构
                    leftmostOfRight.unwrap().borrow_mut().left = Option::None;
                    // 轮到处理自己
                    sum += cur.as_ref().unwrap().borrow().val;
                    cur.as_ref().unwrap().borrow_mut().val = sum;
                    // 自己也遍历完了,所以该遍历比自己小的
                    cur = cur.unwrap().borrow().left.clone();
                }
            }
        }

        root
    }
}

在这里插入图片描述


原题传送门:https://leetcode-cn.com/problems/w6cpku/

原题传送门:https://leetcode-cn.com/problems/convert-bst-to-greater-tree/

原题传送门:https://leetcode-cn.com/problems/binary-search-tree-to-greater-sum-tree/


非常感谢你阅读本文~
放弃不难,但坚持一定很酷~
希望我们大家都能每天进步一点点~
本文由 二当家的白帽子:https://developer.aliyun.com/profile/sqd6avc7qgj7y 博客原创~

相关文章
|
4天前
|
算法 关系型数据库 MySQL
分布式唯一ID生成:深入理解Snowflake算法在Go中的实现
在分布式系统中,确保每个节点生成的 ID 唯一且高效至关重要。Snowflake 算法由 Twitter 开发,通过 64 位 long 型数字生成全局唯一 ID,包括 1 位标识位、41 位时间戳、10 位机器 ID 和 12 位序列号。该算法具备全局唯一性、递增性、高可用性和高性能,适用于高并发场景,如电商促销时的大量订单生成。本文介绍了使用 Go 语言的 `bwmarrin/snowflake` 和 `sony/sonyflake` 库实现 Snowflake 算法的方法。
16 1
分布式唯一ID生成:深入理解Snowflake算法在Go中的实现
|
1月前
|
算法 安全 Go
RSA加密算法详解与Python和Go实现
RSA加密算法详解与Python和Go实现
94 1
|
1月前
|
Rust 监控 编译器
解密 Python 如何调用 Rust 编译生成的动态链接库(一)
解密 Python 如何调用 Rust 编译生成的动态链接库(一)
37 2
|
1月前
|
Rust 安全 Python
解密 Python 如何调用 Rust 编译生成的动态链接库(二)
解密 Python 如何调用 Rust 编译生成的动态链接库(二)
29 1
|
2月前
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
在Android应用开发中,追求卓越性能是不变的主题。本文介绍如何利用Android NDK(Native Development Kit)结合Java与C++进行混合编程,提升应用性能。从环境搭建到JNI接口设计,再到实战示例,全面展示NDK的优势与应用技巧,助你打造高性能应用。通过具体案例,如计算斐波那契数列,详细讲解Java与C++的协作流程,帮助开发者掌握NDK开发精髓,实现高效计算与硬件交互。
129 1
|
1月前
|
安全 测试技术 Go
Python 和 Go 实现 AES 加密算法的技术详解
Python 和 Go 实现 AES 加密算法的技术详解
75 0
|
3月前
|
Rust 安全 Java
Java代码规范--排版,命名.:Rust能否撼动C++的王座?
系统编程是计算机科学的核心,C++长期占据主导地位,但其内存安全问题备受诟病。Rust以安全性为核心,通过所有权和生命周期概念避免了野指针和内存泄漏。此外,Rust的并发模型和日益丰富的生态系统使其成为现代系统编程的新选择,尤其在安全性和并发性方面表现出色。尽管C++依然强大,但Rust为开发者提供了更安全、易管理的选项,未来有望推动更多系统级应用的发展。
26 0
|
4月前
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
【7月更文挑战第28天】在 Android 开发中, NDK 让 Java 与 C++ 混合编程成为可能, 从而提升应用性能。**为何选 NDK?** C++ 在执行效率与内存管理上优于 Java, 特别适合高性能需求场景。**环境搭建** 需 Android Studio 和 NDK, 工具如 CMake。**JNI** 构建 Java-C++ 交互, 通过声明 `native` 方法并在 C++ 中实现。**实战** 示例: 使用 C++ 计算斐波那契数列以提高效率。**总结** 混合编程增强性能, 但增加复杂性, 使用前需谨慎评估。
140 4
|
3月前
|
算法 Java Linux
Intellij Java JNI 调用 C++
Intellij Java JNI 调用 C++
38 0
|
3月前
|
Rust JavaScript Java
简单对比Java、Python、Go、Rust等常见语言计算斐波拉契数的性能
简单对比Java、Python、Go、Rust等常见语言计算斐波拉契数的性能