【算法学习】剑指 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 博客原创~

相关文章
|
2月前
|
安全 Java 编译器
对比Java学习Go——基础理论篇
本章介绍了Java开发者学习Go语言的必要性。Go语言以简单、高效、并发为核心设计哲学,摒弃了传统的类继承和异常机制,采用组合、接口和多返回值错误处理,提升了代码清晰度与开发效率。Go直接编译为静态二进制文件,启动迅速、部署简便,其基于Goroutine和Channel的并发模型相较Java的线程与锁机制更轻量安全。此外,Go Modules简化了依赖管理,与Java的Maven/Gradle形成鲜明对比,提升了构建与部署效率。
|
3月前
|
机器学习/深度学习 算法 数据挖掘
没发论文的注意啦!重磅更新!GWO-BP-AdaBoost预测!灰狼优化、人工神经网络与AdaBoost集成学习算法预测研究(Matlab代码实现)
没发论文的注意啦!重磅更新!GWO-BP-AdaBoost预测!灰狼优化、人工神经网络与AdaBoost集成学习算法预测研究(Matlab代码实现)
150 0
|
6月前
|
人工智能 Kubernetes Java
回归开源,两位 Java 和 Go 程序员分享的开源贡献指引
Higress是一个基于Istio和Envoy的云原生API网关,支持AI功能扩展。它通过Go/Rust/JS编写的Wasm插件提供可扩展架构,并包含Node和Java的console模块。Higress起源于阿里巴巴,解决了Tengine配置重载及gRPC/Dubbo负载均衡问题,现已成为阿里云API网关的基础。本文介绍Higress的基本架构、功能(如AI网关、API管理、Ingress流量网关等)、部署方式以及如何参与开源贡献。此外,还提供了有效的开源贡献指南和社区交流信息。
652 33
|
2月前
|
机器学习/深度学习 运维 算法
【微电网多目标优化调度】多目标学习者行为优化算法MOLPB求解微电网多目标优化调度研究(Matlab代码实现)
【微电网多目标优化调度】多目标学习者行为优化算法MOLPB求解微电网多目标优化调度研究(Matlab代码实现)
204 1
|
2月前
|
存储 Java Go
对比Java学习Go——函数、集合和OOP
Go语言的函数支持声明与调用,具备多返回值、命名返回值等特性,结合`func`关键字与类型后置语法,使函数定义简洁直观。函数可作为一等公民传递、赋值或作为参数,支持匿名函数与闭包。Go通过组合与接口实现面向对象编程,结构体定义数据,方法定义行为,接口实现多态,体现了Go语言的简洁与高效设计。
|
2月前
|
存储 Java 编译器
对比Java学习Go——程序结构与变量
本节对比了Java与Go语言的基础结构,包括“Hello, World!”程序、代码组织方式、入口函数定义、基本数据类型及变量声明方式。Java强调严格的面向对象结构,所有代码需置于类中,入口方法需严格符合`public static void main(String[] args)`格式;而Go语言结构更简洁,使用包和函数组织代码,入口函数为`func main()`。两种语言在变量声明、常量定义、类型系统等方面也存在显著差异,体现了各自的设计哲学。
|
4月前
|
Java Shell Maven
【Azure Container App】构建Java应用镜像时候遇无法编译错误:ERROR [build 10/10] RUN ./mvnw.cmd dependency:go-offline -B -Dproduction package
在部署Java应用到Azure Container App时,构建镜像过程中出现错误:“./mvnw.cmd: No such file or directory”。尽管项目根目录包含mvnw和mvnw.cmd文件,但依然报错。问题出现在Dockerfile构建阶段执行`./mvnw dependency:go-offline`命令时,系统提示找不到可执行文件。经过排查,确认是mvnw文件内容异常所致。最终通过重新生成mvnw文件解决该问题,镜像成功构建。
170 1
|
4月前
|
存储 监控 算法
基于跳表数据结构的企业局域网监控异常连接实时检测 C++ 算法研究
跳表(Skip List)是一种基于概率的数据结构,适用于企业局域网监控中海量连接记录的高效处理。其通过多层索引机制实现快速查找、插入和删除操作,时间复杂度为 $O(\log n)$,优于链表和平衡树。跳表在异常连接识别、黑名单管理和历史记录溯源等场景中表现出色,具备实现简单、支持范围查询等优势,是企业网络监控中动态数据管理的理想选择。
148 0
|
6月前
|
监控 算法 数据处理
基于 C++ 的 KD 树算法在监控局域网屏幕中的理论剖析与工程实践研究
本文探讨了KD树在局域网屏幕监控中的应用,通过C++实现其构建与查询功能,显著提升多维数据处理效率。KD树作为一种二叉空间划分结构,适用于屏幕图像特征匹配、异常画面检测及数据压缩传输优化等场景。相比传统方法,基于KD树的方案检索效率提升2-3个数量级,但高维数据退化和动态更新等问题仍需进一步研究。未来可通过融合其他数据结构、引入深度学习及开发增量式更新算法等方式优化性能。
182 17
|
5月前
|
机器学习/深度学习 存储 算法
基于 C++ 布隆过滤器算法的局域网上网行为控制:URL 访问过滤的高效实现研究
本文探讨了一种基于布隆过滤器的局域网上网行为控制方法,旨在解决传统黑白名单机制在处理海量URL数据时存储与查询效率低的问题。通过C++实现URL访问过滤功能,实验表明该方法可将内存占用降至传统方案的八分之一,查询速度提升约40%,假阳性率可控。研究为优化企业网络管理提供了新思路,并提出结合机器学习、改进哈希函数及分布式协同等未来优化方向。
163 0