0

0

深入理解 LeetCode 1038:利用反向中序遍历将二叉搜索树转换为累加树

聖光之護

聖光之護

发布时间:2025-11-21 19:24:01

|

756人浏览过

|

来源于php中文网

原创

深入理解 leetcode 1038:利用反向中序遍历将二叉搜索树转换为累加树

本教程深入探讨 LeetCode 1038 题,讲解如何将二叉搜索树(BST)转换为累加树(Greater Tree)。核心方法是利用 BST 的特性,通过一次反向中序遍历(右-根-左)来高效更新节点值。文章将详细解析递归函数的运作机制,特别是 `return go(root.left, root.val)` 语句如何确保累加和的正确传递,并提供示例代码和解释,帮助读者掌握这一经典算法模式。

在二叉搜索树(BST)中,每个节点的值都大于其左子树中的所有节点值,且小于其右子树中的所有节点值。将 BST 转换为累加树(Greater Tree)的目标是使每个节点的新值等于其原始值加上所有大于或等于其原始值的节点值之和。例如,如果一个 BST 节点值为 X,转换后它的新值将是 X + (所有大于 X 的节点值之和)。

核心思想:反向中序遍历

解决此问题的关键在于利用 BST 的有序性。如果我们按照从大到小的顺序遍历 BST 中的所有节点,就可以在遍历过程中累加一个总和,并将这个总和加到当前节点上。这种从大到小的遍历顺序恰好是“反向中序遍历”(Right -> Root -> Left)。

  1. 访问右子树: 首先访问右子树,因为右子树中的所有节点都比当前根节点大。
  2. 访问根节点: 接着访问根节点。此时,我们已经处理了所有比根节点大的节点(即右子树中的节点),并累加了它们的和。我们将这个累加和加到根节点上。
  3. 访问左子树: 最后访问左子树。左子树中的所有节点都比当前根节点小,但它们仍然需要加上之前累积的总和(包括根节点及其右子树的和)。

算法实现与代码解析

以下是实现这一转换的 JavaScript 代码:

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
const bstToGst = (root) => {
    // 辅助函数,执行反向中序遍历并更新节点值
    // sum 参数用于累积到当前节点为止所有大于等于当前节点值的总和
    function go(node, sum) {
        // 1. 基本情况:如果当前节点为空,则返回当前的累加和
        if (!node) {
            return sum;
        }

        // 2. 递归处理右子树:
        // 先处理右子树,并将右子树处理完后返回的累加和(即所有比当前节点大的节点值之和)
        // 加到当前节点的 val 上。
        // go(node.right, sum) 返回的是处理完右子树后,最新的累加和。
        node.val += go(node.right, sum);

        // 3. 递归处理左子树:
        // 当前节点的 val 已经更新为“原始值 + 所有比它大的节点值之和”。
        // 这个更新后的 node.val 就是新的累加和,需要传递给左子树。
        // 因为左子树中的所有节点都比当前 node 小,但它们需要加上 node 当前的累加值。
        // go(node.left, node.val) 将处理左子树,并返回处理完左子树后,最新的累加和。
        // 这个返回值将继续向上传递。
        return go(node.left, node.val);
    }

    // 从根节点开始调用辅助函数,初始累加和为 0
    go(root, 0);
    // 返回已经转换完成的根节点
    return root;
};

关键语句 return go(node.left, node.val); 详解:

Interior AI
Interior AI

AI室内设计,上传室内照片自动帮你生成多种风格的室内设计图

下载

理解 go 函数中 sum 参数的含义和返回值至关重要。

  • sum 参数的含义: go(node, sum) 中的 sum 参数代表的是在遍历到 node 之前,所有已经处理过的、且值大于或等于 node 原始值的节点之和。换句话说,它是“当前节点右侧(或更右侧)所有节点的累加和”。
  • node.val += go(node.right, sum);:
    • go(node.right, sum) 会递归地处理 node 的整个右子树。当这个调用返回时,它返回的 sum 是在处理完 node 的右子树中最右侧的节点后,累积到的总和。这个总和包含了 sum 参数的初始值以及 node 右子树中所有节点的值。
    • 将这个返回的 sum 加到 node.val 上,使得 node.val 更新为 node 的原始值加上所有比 node 大的节点值之和(包括右子树中的节点以及 sum 参数带来的更右侧的累加)。
  • return go(node.left, node.val);:
    • 此时,node.val 已经包含了 node 原始值以及所有比它大的节点值之和。这个更新后的 node.val 就是新的“累加和”,它将作为参数传递给 node 的左子树。
    • go(node.left, node.val) 会递归地处理 node 的整个左子树。左子树中的每个节点都会将这个 node.val 作为其初始累加和。
    • 最终,go(node.left, node.val) 会返回处理完整个左子树后,累积到的最终总和。这个总和包含了 node 及其右子树、node 自身以及 node 左子树中所有节点的值。
    • 这个返回值正是当前 go(node, sum) 函数需要向其调用者返回的值,以继续向上传递累积的总和。它确保了整个树的遍历过程中,sum 始终代表着当前节点及其右侧所有节点的累加值,正确地传递给下一个需要更新的节点。

示例解析

让我们使用提供的示例树来追踪 bstToGst 函数的执行过程:

      4
    /   \
  1       6
 / \     / \
0   2   5   7
     \       \
      3       8

初始调用:bstToGst(root_4) 会调用 go(root_4, 0)。

  1. go(root_4, 0):
    • 调用 go(root_4.right, 0),即 go(root_6, 0)。
  2. go(root_6, 0):
    • 调用 go(root_6.right, 0),即 go(root_7, 0)。
  3. go(root_7, 0):
    • 调用 go(root_7.right, 0),即 go(root_8, 0)。
  4. go(root_8, 0):
    • 调用 go(root_8.right, 0),即 go(null, 0) -> 返回 0。
    • root_8.val += 0 (8 + 0 = 8)。root_8.val 更新为 8。
    • 调用 go(root_8.left, 8),即 go(null, 8) -> 返回 8。
    • go(root_8, 0) 返回 8。
  5. go(root_7, 0) 收到 8:
    • root_7.val += 8 (7 + 8 = 15)。root_7.val 更新为 15。
    • 调用 go(root_7.left, 15),即 go(null, 15) -> 返回 15。
    • go(root_7, 0) 返回 15。
  6. go(root_6, 0) 收到 15:
    • root_6.val += 15 (6 + 15 = 21)。root_6.val 更新为 21。
    • 调用 go(root_6.left, 21),即 go(root_5, 21)。
  7. go(root_5, 21):
    • 调用 go(root_5.right, 21),即 go(null, 21) -> 返回 21。
    • root_5.val += 21 (5 + 21 = 26)。root_5.val 更新为 26。
    • 调用 go(root_5.left, 26),即 go(null, 26) -> 返回 26。
    • go(root_5, 21) 返回 26。
  8. go(root_6, 0) 收到 26:
    • go(root_6, 0) 返回 26。
  9. go(root_4, 0) 收到 26:
    • root_4.val += 26 (4 + 26 = 30)。root_4.val 更新为 30。
    • 调用 go(root_4.left, 30),即 go(root_1, 30)。
  10. go(root_1, 30):
    • 调用 go(root_1.right, 30),即 go(root_2, 30)。
  11. go(root_2, 30):
    • 调用 go(root_2.right, 30),即 go(root_3, 30)。
  12. go(root_3, 30):
    • 调用 go(root_3.right, 30),即 go(null, 30) -> 返回 30。
    • root_3.val += 30 (3 + 30 = 33)。root_3.val 更新为 33。
    • 调用 go(root_3.left, 33),即 go(null, 33) -> 返回 33。
    • go(root_3, 30) 返回 33。
  13. go(root_2, 30) 收到 33:
    • root_2.val += 33 (2 + 33 = 35)。root_2.val 更新为 35。
    • 调用 go(root_2.left, 35),即 go(null, 35) -> 返回 35。
    • go(root_2, 30) 返回 35。
  14. go(root_1, 30) 收到 35:
    • root_1.val += 35 (1 + 35 = 36)。root_1.val 更新为 36。
    • 调用 go(root_1.left, 36),即 go(root_0, 36)。
  15. go(root_0, 36):
    • 调用 go(root_0.right, 36),即 go(null, 36) -> 返回 36。
    • root_0.val += 36 (0 + 36 = 36)。root_0.val 更新为 36。
    • 调用 go(root_0.left, 36),即 go(null, 36) -> 返回 36。
    • go(root_0, 36) 返回 `36

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

553

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

374

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

731

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

477

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

394

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

990

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

656

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

551

2023.09.20

Golang gRPC 服务开发与Protobuf实战
Golang gRPC 服务开发与Protobuf实战

本专题系统讲解 Golang 在 gRPC 服务开发中的完整实践,涵盖 Protobuf 定义与代码生成、gRPC 服务端与客户端实现、流式 RPC(Unary/Server/Client/Bidirectional)、错误处理、拦截器、中间件以及与 HTTP/REST 的对接方案。通过实际案例,帮助学习者掌握 使用 Go 构建高性能、强类型、可扩展的 RPC 服务体系,适用于微服务与内部系统通信场景。

0

2026.01.15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 3.6万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.2万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 2.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号