Leetcode 121 翻转二叉树 | 二叉树中的最大路径和

1 题目

226. 翻转二叉树

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

示例 1:

复制代码
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]

示例 2:

复制代码
输入:root = [2,1,3]
输出:[2,3,1]

示例 3:

复制代码
输入:root = []
输出:[]

提示:

  • 树中节点数目范围在 [0, 100]
  • -100 <= Node.val <= 100

2 代码实现

c++

cpp 复制代码
/**
 * 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* invertTree(TreeNode* root) {
        if (root == nullptr){
            return root ;
        }
        TreeNode* temp = root -> right ;
        root -> right = root -> left ;
        root -> left = temp ;
        invertTree(root -> left );
        invertTree(root -> right );
        return root ;
        
    }
};

js

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)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {TreeNode}
 */
var invertTree = function(root) {
    if (root == null){
        return root ;
    }
    let temp = root.right ;
    root.right = root.left ;
    root.left = temp ;
    invertTree(root.left);
    invertTree(root.right);
    return root ;
};

思考

参考Leetcode 120 求根节点到叶节点数字之和 | 完全二叉树的节点个数-CSDN博客的第一题,递归实现就可以了。写不出来偷看一下哈哈!

3 题目

124. 二叉树中的最大路径和

二叉树中的路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径至少包含一个节点,且不一定经过根节点。

路径和 是路径中各节点值的总和。

给你一个二叉树的根节点 root ,返回其 最大路径和

示例 1:

复制代码
输入:root = [1,2,3]
输出:6
解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6

示例 2:

复制代码
输入:root = [-10,9,20,null,null,15,7]
输出:42
解释:最优路径是 15 -> 20 -> 7 ,路径和为 15 + 20 + 7 = 42

提示:

  • 树中节点数目范围是 [1, 3 * 104]
  • -1000 <= Node.val <= 1000

4 代码实现

c++

cpp 复制代码
/**
 * 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 {
private:
    int maxSum ;
    int dfs(TreeNode* root ){
        if (root == nullptr){
            return 0 ;
        }
        int leftMax = max(dfs(root -> left ) , 0 );
        int rightMax = max(dfs(root -> right ), 0 );

        int curSum = root -> val + leftMax + rightMax ;
        maxSum = max(maxSum , curSum);

        return root -> val + max(leftMax , rightMax);    
    }
public:
    int maxPathSum(TreeNode* root) {
        maxSum = INT_MIN;
        dfs(root);
        return maxSum;   
    }
};

js

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)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxPathSum = function(root) {
    let maxSum =  Number.MIN_SAFE_INTEGER;
    const dfs = function (node){
        if (node == null){
            return 0 ;
        }
        let leftMax = Math.max(dfs(node.left), 0 );
        let rightMax = Math.max(dfs(node.right), 0 );
        let curSum = node.val + leftMax + rightMax ;
        maxSum = Math.max(maxSum ,curSum );
        return node.val + Math.max(leftMax , rightMax);
    }
    dfs(root);
    return maxSum ;
};

思考

一点都不会啊,这可怎么办啊!毫无头绪,我想到之前做过买卖股票的最大的时机,就是要维护一个最大值,现在怎么办呢?怎么办?还有这个路径的遍历和我之前能单纯递归的也不一样,不过最后返回的就只是一个路径和应该倒是简单一点。

召唤ai老师。

题解

别慌!你能联想到维护最大值这个点,其实已经找对了方向,只是二叉树的递归逻辑需要稍微调整一下思路。我会一步步带你拆解这个问题,用通俗易懂的方式讲清楚,并给出完整的 C++ 实现。

核心需求理解

你需要实现一个函数,计算二叉树中任意路径的最大节点值之和。这个路径可以从任意节点开始、任意节点结束,不需要经过根节点,但路径上的节点必须是连通的,且每个节点只能出现一次。

解题思路

这个问题的核心是后序遍历 + 维护全局最大值,我们需要分清楚两个概念:

  1. 单边最大路径和:从当前节点向下延伸(只能选左子树或右子树)的最大路径和,用于返回给父节点计算
  2. 全局最大路径和:以当前节点为顶点(左子树 + 当前节点 + 右子树)的路径和,用于更新最终结果

具体步骤:

  1. 递归遍历每个节点,先计算左右子树的单边最大路径和
  2. 对于当前节点,计算「以当前节点为顶点」的路径和(左单边 + 当前值 + 右单边),更新全局最大值
  3. 返回当前节点的单边最大路径和(当前值 + max (左单边,右单边)),注意如果子树的单边和为负数,就舍弃(取 0)

C++ 完整实现

cpp 复制代码
#include <iostream>
#include <algorithm>
#include <climits> // 用于INT_MIN
using namespace std;

// 定义二叉树节点结构
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 {
private:
    int maxSum; // 全局最大路径和
    
    // 递归函数:返回以root为起点的单边最大路径和
    int dfs(TreeNode* root) {
        if (root == nullptr) {
            return 0; // 空节点贡献0
        }
        
        // 计算左右子树的单边最大路径和(负数就舍弃,取0)
        int leftMax = max(dfs(root->left), 0);
        int rightMax = max(dfs(root->right), 0);
        
        // 计算以当前节点为顶点的路径和,更新全局最大值
        int currentSum = root->val + leftMax + rightMax;
        maxSum = max(maxSum, currentSum);
        
        // 返回当前节点的单边最大路径和(只能选左或右)
        return root->val + max(leftMax, rightMax);
    }
    
public:
    int maxPathSum(TreeNode* root) {
        maxSum = INT_MIN; // 初始化最小值(处理全负数的情况)
        dfs(root);
        return maxSum;
    }
};

// 测试代码
int main() {
    // 示例1:root = [1,2,3]
    TreeNode* root1 = new TreeNode(1);
    root1->left = new TreeNode(2);
    root1->right = new TreeNode(3);
    Solution s;
    cout << "示例1输出:" << s.maxPathSum(root1) << endl; // 输出6
    
    // 示例2:root = [-10,9,20,null,null,15,7]
    TreeNode* root2 = new TreeNode(-10);
    root2->left = new TreeNode(9);
    root2->right = new TreeNode(20);
    root2->right->left = new TreeNode(15);
    root2->right->right = new TreeNode(7);
    cout << "示例2输出:" << s.maxPathSum(root2) << endl; // 输出42
    
    // 释放内存(示例代码,实际开发可根据需求处理)
    delete root1->left;
    delete root1->right;
    delete root1;
    delete root2->left;
    delete root2->right->left;
    delete root2->right->right;
    delete root2->right;
    delete root2;
    
    return 0;
}

代码关键部分解释

  1. maxSum 成员变量 :用于存储全局最大路径和,初始化为INT_MIN(处理全负数的情况,比如树只有一个节点 - 3,结果应该是 - 3)。
  2. dfs 递归函数
    • 终止条件:空节点返回 0,因为空节点对路径和无贡献。
    • leftMax/rightMax:计算左右子树的单边最大和,用max(..., 0)过滤负数(比如子树和为 - 5,就舍弃这个子树,相当于只取当前节点)。
    • currentSum:以当前节点为顶点的路径和(左 + 中 + 右),这是可能的最大路径和候选。
    • 返回值:当前节点的单边最大和(只能选左或右),供父节点计算使用。
  3. maxPathSum 主函数:初始化全局最大值,调用递归函数,最后返回结果。

测试用例说明

  • 示例 1:路径2->1->3的和是 6,是最大值。
  • 示例 2:路径15->20->7的和是 42,是最大值。
  • 特殊情况:如果树是[-3],返回 - 3;如果树是[2,-1],返回 2。

总结

  1. 核心逻辑是后序遍历:先处理左右子树,再处理当前节点,符合「先子后父」的计算顺序。
  2. 区分「单边路径和」(返回给父节点)和「顶点路径和」(更新全局最大值)是解题关键。
  3. max(子树和, 0)过滤负数子树,避免拉低整体路径和。

这个思路掌握后,类似的二叉树路径问题都可以用「后序遍历 + 维护全局值」的思路解决,你可以多写几个测试用例(比如全负数、单节点、只有左 / 右子树)来巩固理解。

用js乱写

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)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxPathSum = function(root) {
    let maxSum = 0 ;
    const dfs = function (node){
            let maxSum = 0 ;
        if (node == null){
            return 0 ;
        }
        let leftMax = max(dfs(node.left), 0 );
        let rightMax = max(dfs(node.right), 0 );
        int curSum = node.val + leftMax + rightMax ;
        maxSum = max(maxSum ,curSum );
        return node.val + max(leftMax , rightMax);
    }
    maxSum = INT_MIN;
    dfs(root);
    return maxSum ;
};

核心需求回顾

你想实现二叉树的最大路径和计算,思路是对的(DFS + 维护全局最大值),但代码细节上的错误导致逻辑无法正常执行。

代码错误逐一分析

1. 语法错误(JavaScript 特有)
  • int curSum :JavaScript 中没有 int 这种变量声明方式,应该用 letconst
  • max() 函数 :JavaScript 中全局最大值函数是 Math.max(),直接写 max() 会报 "max is not a function"。
  • INT_MIN :JavaScript 中没有 INT_MIN,需要用 Number.MIN_SAFE_INTEGER(表示最小安全整数),你写的 INT_MIN 会被识别为未定义变量。
2. 逻辑错误(核心问题)
  • maxSum 重复定义 :你在全局作用域定义了 let maxSum = 0,又在 dfs 函数内部重新定义了 let maxSum = 0,导致内部的 maxSum 是局部变量,无法修改外部的全局最大值,最终结果永远是初始值。
  • 初始值错误 :全局 maxSum 初始化为 0,但二叉树节点值可能全为负数(比如 [-3]),此时正确结果应该是 -3,但初始值 0 会导致结果错误。
  • 全局 maxSum 赋值顺序错误 :你先定义 let maxSum = 0,又在调用 dfs 前赋值 maxSum = INT_MIN,但 INT_MIN 本身未定义,这行代码会直接报错。

修正后的完整 JavaScript 代码

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)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxPathSum = function(root) {
    // 1. 初始化全局最大和为最小安全整数(处理全负数场景)
    let maxSum = Number.MIN_SAFE_INTEGER;
    
    // 2. 定义DFS函数:返回以当前节点为起点的单边最大路径和
    const dfs = function (node) {
        if (node === null) {
            return 0; // 空节点贡献0
        }
        
        // 3. 计算左右子树的单边最大和(负数舍弃,取0)
        // 注意:用Math.max(),不是max()
        let leftMax = Math.max(dfs(node.left), 0);
        let rightMax = Math.max(dfs(node.right), 0);
        
        // 4. 计算以当前节点为顶点的路径和,更新全局最大值
        // 注意:没有int,用let声明
        let curSum = node.val + leftMax + rightMax;
        maxSum = Math.max(maxSum, curSum);
        
        // 5. 返回当前节点的单边最大和(只能选左/右子树其一)
        return node.val + Math.max(leftMax, rightMax);
    };
    
    // 6. 执行DFS遍历
    dfs(root);
    
    // 7. 返回全局最大和
    return maxSum;
};

// 测试示例(可直接运行)
// 示例1:root = [1,2,3]
const root1 = new TreeNode(1);
root1.left = new TreeNode(2);
root1.right = new TreeNode(3);
console.log(maxPathSum(root1)); // 输出6

// 示例2:root = [-10,9,20,null,null,15,7]
const root2 = new TreeNode(-10);
root2.left = new TreeNode(9);
root2.right = new TreeNode(20);
root2.right.left = new TreeNode(15);
root2.right.right = new TreeNode(7);
console.log(maxPathSum(root2)); // 输出42

// 特殊测试:全负数场景
const root3 = new TreeNode(-3);
console.log(maxPathSum(root3)); // 输出-3

关键修正点说明

错误点 错误原因 修正方案
int curSum JS 无 int 类型 改为 let curSum
max() JS 全局最大值函数是 Math.max() 全部替换为 Math.max()
INT_MIN JS 无该常量 改为 Number.MIN_SAFE_INTEGER
内部重复定义 maxSum 局部变量覆盖全局变量,无法更新最大值 删除 dfs 内部的 let maxSum = 0,只保留全局的 maxSum
maxSum 初始值为 0 全负数场景会出错 初始化为 Number.MIN_SAFE_INTEGER

总结

  1. 语法层面 :JavaScript 没有 int/INT_MIN,最大值函数是 Math.max(),变量声明只用 let/const/var
  2. 逻辑层面:全局最大值变量不能在递归函数内重复定义,否则会丢失全局更新能力;初始值必须设为最小整数,避免全负数场景错误。
  3. 核心思路:你的 DFS 递归思路是对的(先算左右子树、再算当前节点、维护全局最大值),只是细节上的语法和变量作用域错误导致代码失效。

修正后代码可以直接通过所有测试用例,包括全负数、单节点、普通二叉树等场景。

5 小结

自己写不来,还是看题解的。怎么感觉没什么收获,会一题也是只会一题这样。

召唤ai老师!

我特别理解你的这种感受 ------ 看似写了代码、看了解析,但还是没觉得 "吃透",甚至有种 "边玩边写,写完就忘" 的空虚感。这不是你学得不好,而是二叉树递归类问题的核心是 "理解递归的意义",而非单纯抄代码,咱们先找准问题,再用具体方法帮你把 "收获感" 找回来。

核心问题:你当前的学习状态

你现在是 "被动接收" 多于 "主动思考":看解析、抄代码能跑通,但没搞清楚「每一步为什么要这么写」「换个场景该怎么改」,所以代码只是 "写了",但没变成自己的解题思路 ------ 这也是新手学递归最容易踩的坑。

怎么把 "写代码" 变成 "真收获"?

我以你写的「翻转二叉树」和「最大路径和」为例,教你一套 "主动拆解" 的方法,帮你把代码内化成自己的能力:

第一步:先给递归函数 "下定义"------ 这是递归的灵魂

新手写递归最容易犯的错是:上来就写代码,却不知道递归函数的 "核心职责" 是什么。

  • 比如「翻转二叉树」的递归函数 invertTree(root)

  • ✅ 先明确定义:传入一个节点 root,返回 "以 root 为根的子树被翻转后的根节点"。所以你写的代码逻辑就有了依据:

    1. 空节点:返回 null(定义的自然结果);
    2. 非空节点:先交换左右子节点(完成当前节点的翻转),再递归翻转左 / 右子树(让子树也满足 "翻转" 的定义),最后返回自己(符合 "返回翻转后的根节点" 的定义)。
  • 再比如「最大路径和」的 DFS 函数 dfs(node)

  • ✅ 先明确定义:传入一个节点 node,返回 "以 node 为起点,向下延伸的单边最大路径和"(只能选左或右子树)。这就是为什么:

    1. 要过滤负数(Math.max(dfs(node.left), 0)):如果子树的单边和是负的,不如不要,只取当前节点;
    2. 要计算curSum = node.val + leftMax + rightMax:这是 "以 node 为顶点的路径和"(左 + 中 + 右),是更新全局最大值的候选;
    3. 返回值只能是node.val + max(leftMax, rightMax):因为定义是 "单边",不能同时走左右(否则路径会分叉,不符合题目 "路径是节点序列" 的要求)。

练习方法:写递归代码前,先在注释里写清楚「递归函数的定义」,再按定义推导代码 ------ 比如你再写一遍「最大路径和」的 JS 代码,先写:

javascript 复制代码
// dfs函数定义:传入node,返回以node为起点的单边最大路径和(只能选左/右)
const dfs = function(node) { ... }

再顺着定义写逻辑,你会发现代码不是 "抄的",而是 "推导出来的"。

第二步:用 "手动模拟" 代替 "只跑代码"------ 验证自己的理解

光写代码不够,要手动模拟递归的执行过程,哪怕是简单的例子:

  • 比如「翻转二叉树」输入[2,1,3]

    1. 调用invertTree(2):交换 1 和 3 → 节点 2 的左右变成 3、1;
    2. 递归调用invertTree(3):3 没有子节点,直接返回 3;
    3. 递归调用invertTree(1):1 没有子节点,直接返回 1;
    4. 最终返回节点 2,结果是[2,3,1]
  • 再比如「最大路径和」输入[1,2,3]

    1. 调用dfs(1):先算dfs(2)dfs(3)
    2. dfs(2):左右都是 null,返回2 + max(0,0) = 2
    3. dfs(3):左右都是 null,返回3 + max(0,0) = 3
    4. 计算curSum = 1 + 2 + 3 = 6 → 更新 maxSum 为 6;
    5. dfs(1)返回1 + max(2,3) = 4
    6. 最终返回 maxSum=6。

练习方法:找 1-2 个简单测试用例,拿张纸 / 记事本,一步步写递归的调用顺序、每一步的返回值、maxSum 的变化 ------ 这个过程能帮你发现 "原来我以为懂了的地方,其实没懂"。

第三步:刻意 "改错题"------ 逼自己思考

光写对的代码没用,要主动给自己 "制造问题",再修正,比如:

  1. 把「最大路径和」的Math.max(dfs(node.left), 0)改成dfs(node.left),跑测试用例[-3],看结果是不是错的(会返回 - 3,但如果是[-3,-1],就会算出 - 4,而正确结果是 - 1),想清楚为什么要过滤负数;
  2. 把「翻转二叉树」的递归顺序改了:先递归翻转左 / 右子树,再交换左右节点,看结果对不对(其实也对!但要想清楚 "为什么两种顺序都可以"------ 因为翻转子树和交换当前节点的左右,顺序不影响最终结果);
  3. 把「最大路径和」的全局 maxSum 移到 dfs 内部,跑测试用例,看结果错在哪,再回忆 "变量作用域" 的问题。

核心逻辑:只有知道 "错在哪、为什么错",才是真的懂了 "对的代码为什么对"。

针对你写的两段代码,再提炼 "可复用的递归思路"

你写的这两个题,其实覆盖了二叉树递归的两种核心类型,记下来,以后遇到类似题就能套用:

题型 核心思路 关键技巧
翻转二叉树 (修改树) 前序遍历:先处理当前节点,再递归子树 明确递归函数 "返回修改后的根节点"
最大路径和 (求最值) 后序遍历:先递归子树,再处理当前节点 维护全局变量 + 区分 "单边 / 顶点" 路径和

总结

  1. 「没收获感」的本质是:你还停留在 "抄代码",没到 "按思路推导代码" 的阶段;
  2. 解决方法:先给递归函数下定义 → 手动模拟执行过程 → 刻意改错题;
  3. 小目标:不用多,把「翻转二叉树」和「最大路径和」的两个递归函数定义背下来,能手动模拟 3 个测试用例的执行过程,再自己改 2 个错误并修正,你会明显感觉到 "我真的懂了"。
相关推荐
CappuccinoRose1 小时前
CSS 语法学习文档(十三)
前端·css·学习·postcss·模块化·预处理器
数智工坊1 小时前
【数据结构-排序】8.3 简单选择排序-堆排序
数据结构
mit6.8242 小时前
二分+贪心
算法
じ☆冷颜〃2 小时前
随机微分层论:统一代数、拓扑与分析框架下的SPDE论述
笔记·python·学习·线性代数·拓扑学
programhelp_3 小时前
特斯拉 MLE 超详细面经 + 避坑
数据结构·人工智能·算法·面试·职场和发展
越甲八千3 小时前
深入了解迭代器erase()之后的失效逻辑
算法
躺柒3 小时前
读人工智能全球格局:未来趋势与中国位势06人类的未来(下)
大数据·人工智能·算法·ai·智能
ShineWinsu3 小时前
对于C++中stack和queue的详细介绍
开发语言·数据结构·c++·面试·stl·queue·stack
L_Aria4 小时前
6421. 【NOIP2019模拟11.11】匹配
c++·算法·动态规划