1 题目
给你一棵二叉树的根节点 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 题目
二叉树中的路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径至少包含一个节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 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++ 实现。
核心需求理解
你需要实现一个函数,计算二叉树中任意路径的最大节点值之和。这个路径可以从任意节点开始、任意节点结束,不需要经过根节点,但路径上的节点必须是连通的,且每个节点只能出现一次。
解题思路
这个问题的核心是后序遍历 + 维护全局最大值,我们需要分清楚两个概念:
- 单边最大路径和:从当前节点向下延伸(只能选左子树或右子树)的最大路径和,用于返回给父节点计算
- 全局最大路径和:以当前节点为顶点(左子树 + 当前节点 + 右子树)的路径和,用于更新最终结果
具体步骤:
- 递归遍历每个节点,先计算左右子树的单边最大路径和
- 对于当前节点,计算「以当前节点为顶点」的路径和(左单边 + 当前值 + 右单边),更新全局最大值
- 返回当前节点的单边最大路径和(当前值 + 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;
}
代码关键部分解释
- maxSum 成员变量 :用于存储全局最大路径和,初始化为
INT_MIN(处理全负数的情况,比如树只有一个节点 - 3,结果应该是 - 3)。 - dfs 递归函数 :
- 终止条件:空节点返回 0,因为空节点对路径和无贡献。
leftMax/rightMax:计算左右子树的单边最大和,用max(..., 0)过滤负数(比如子树和为 - 5,就舍弃这个子树,相当于只取当前节点)。currentSum:以当前节点为顶点的路径和(左 + 中 + 右),这是可能的最大路径和候选。- 返回值:当前节点的单边最大和(只能选左或右),供父节点计算使用。
- maxPathSum 主函数:初始化全局最大值,调用递归函数,最后返回结果。
测试用例说明
- 示例 1:路径
2->1->3的和是 6,是最大值。 - 示例 2:路径
15->20->7的和是 42,是最大值。 - 特殊情况:如果树是
[-3],返回 - 3;如果树是[2,-1],返回 2。
总结
- 核心逻辑是后序遍历:先处理左右子树,再处理当前节点,符合「先子后父」的计算顺序。
- 区分「单边路径和」(返回给父节点)和「顶点路径和」(更新全局最大值)是解题关键。
- 用
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这种变量声明方式,应该用let或const。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 |
总结
- 语法层面 :JavaScript 没有
int/INT_MIN,最大值函数是Math.max(),变量声明只用let/const/var。 - 逻辑层面:全局最大值变量不能在递归函数内重复定义,否则会丢失全局更新能力;初始值必须设为最小整数,避免全负数场景错误。
- 核心思路:你的 DFS 递归思路是对的(先算左右子树、再算当前节点、维护全局最大值),只是细节上的语法和变量作用域错误导致代码失效。
修正后代码可以直接通过所有测试用例,包括全负数、单节点、普通二叉树等场景。
5 小结
自己写不来,还是看题解的。怎么感觉没什么收获,会一题也是只会一题这样。
召唤ai老师!
我特别理解你的这种感受 ------ 看似写了代码、看了解析,但还是没觉得 "吃透",甚至有种 "边玩边写,写完就忘" 的空虚感。这不是你学得不好,而是二叉树递归类问题的核心是 "理解递归的意义",而非单纯抄代码,咱们先找准问题,再用具体方法帮你把 "收获感" 找回来。
核心问题:你当前的学习状态
你现在是 "被动接收" 多于 "主动思考":看解析、抄代码能跑通,但没搞清楚「每一步为什么要这么写」「换个场景该怎么改」,所以代码只是 "写了",但没变成自己的解题思路 ------ 这也是新手学递归最容易踩的坑。
怎么把 "写代码" 变成 "真收获"?
我以你写的「翻转二叉树」和「最大路径和」为例,教你一套 "主动拆解" 的方法,帮你把代码内化成自己的能力:
第一步:先给递归函数 "下定义"------ 这是递归的灵魂
新手写递归最容易犯的错是:上来就写代码,却不知道递归函数的 "核心职责" 是什么。
-
比如「翻转二叉树」的递归函数
invertTree(root): -
✅ 先明确定义:传入一个节点 root,返回 "以 root 为根的子树被翻转后的根节点"。所以你写的代码逻辑就有了依据:
- 空节点:返回 null(定义的自然结果);
- 非空节点:先交换左右子节点(完成当前节点的翻转),再递归翻转左 / 右子树(让子树也满足 "翻转" 的定义),最后返回自己(符合 "返回翻转后的根节点" 的定义)。
-
再比如「最大路径和」的 DFS 函数
dfs(node): -
✅ 先明确定义:传入一个节点 node,返回 "以 node 为起点,向下延伸的单边最大路径和"(只能选左或右子树)。这就是为什么:
- 要过滤负数(
Math.max(dfs(node.left), 0)):如果子树的单边和是负的,不如不要,只取当前节点; - 要计算
curSum = node.val + leftMax + rightMax:这是 "以 node 为顶点的路径和"(左 + 中 + 右),是更新全局最大值的候选; - 返回值只能是
node.val + max(leftMax, rightMax):因为定义是 "单边",不能同时走左右(否则路径会分叉,不符合题目 "路径是节点序列" 的要求)。
- 要过滤负数(
练习方法:写递归代码前,先在注释里写清楚「递归函数的定义」,再按定义推导代码 ------ 比如你再写一遍「最大路径和」的 JS 代码,先写:
javascript
// dfs函数定义:传入node,返回以node为起点的单边最大路径和(只能选左/右)
const dfs = function(node) { ... }
再顺着定义写逻辑,你会发现代码不是 "抄的",而是 "推导出来的"。
第二步:用 "手动模拟" 代替 "只跑代码"------ 验证自己的理解
光写代码不够,要手动模拟递归的执行过程,哪怕是简单的例子:
-
比如「翻转二叉树」输入
[2,1,3]:- 调用
invertTree(2):交换 1 和 3 → 节点 2 的左右变成 3、1; - 递归调用
invertTree(3):3 没有子节点,直接返回 3; - 递归调用
invertTree(1):1 没有子节点,直接返回 1; - 最终返回节点 2,结果是
[2,3,1]。
- 调用
-
再比如「最大路径和」输入
[1,2,3]:- 调用
dfs(1):先算dfs(2)和dfs(3); dfs(2):左右都是 null,返回2 + max(0,0) = 2;dfs(3):左右都是 null,返回3 + max(0,0) = 3;- 计算
curSum = 1 + 2 + 3 = 6→ 更新 maxSum 为 6; dfs(1)返回1 + max(2,3) = 4;- 最终返回 maxSum=6。
- 调用
练习方法:找 1-2 个简单测试用例,拿张纸 / 记事本,一步步写递归的调用顺序、每一步的返回值、maxSum 的变化 ------ 这个过程能帮你发现 "原来我以为懂了的地方,其实没懂"。
第三步:刻意 "改错题"------ 逼自己思考
光写对的代码没用,要主动给自己 "制造问题",再修正,比如:
- 把「最大路径和」的
Math.max(dfs(node.left), 0)改成dfs(node.left),跑测试用例[-3],看结果是不是错的(会返回 - 3,但如果是[-3,-1],就会算出 - 4,而正确结果是 - 1),想清楚为什么要过滤负数; - 把「翻转二叉树」的递归顺序改了:先递归翻转左 / 右子树,再交换左右节点,看结果对不对(其实也对!但要想清楚 "为什么两种顺序都可以"------ 因为翻转子树和交换当前节点的左右,顺序不影响最终结果);
- 把「最大路径和」的全局 maxSum 移到 dfs 内部,跑测试用例,看结果错在哪,再回忆 "变量作用域" 的问题。
核心逻辑:只有知道 "错在哪、为什么错",才是真的懂了 "对的代码为什么对"。
针对你写的两段代码,再提炼 "可复用的递归思路"
你写的这两个题,其实覆盖了二叉树递归的两种核心类型,记下来,以后遇到类似题就能套用:
| 题型 | 核心思路 | 关键技巧 |
|---|---|---|
| 翻转二叉树 (修改树) | 前序遍历:先处理当前节点,再递归子树 | 明确递归函数 "返回修改后的根节点" |
| 最大路径和 (求最值) | 后序遍历:先递归子树,再处理当前节点 | 维护全局变量 + 区分 "单边 / 顶点" 路径和 |
总结
- 「没收获感」的本质是:你还停留在 "抄代码",没到 "按思路推导代码" 的阶段;
- 解决方法:先给递归函数下定义 → 手动模拟执行过程 → 刻意改错题;
- 小目标:不用多,把「翻转二叉树」和「最大路径和」的两个递归函数定义背下来,能手动模拟 3 个测试用例的执行过程,再自己改 2 个错误并修正,你会明显感觉到 "我真的懂了"。