二叉树(题单顺序)
热题 100 - 二叉树分组(按题单顺序):94 / 104 / 226 / 101 / 543 / 102 / 108 / 98 / 230 / 199 / 114 / 105 / 437 / 236 / 124
本页包含题单「二叉树」分组的全部题目,顺序与 list.json 保持一致。
说明(点击展开)
为了让测试用例可运行,本页代码里会自带 TreeNode、buildTree 等小工具(不影响 LeetCode 提交)。
94. 二叉树的中序遍历
给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
示例 1:
输入:root = [1,null,2,3] 输出:[1,3,2]
示例 2:
输入:root = [] 输出:[]
示例 3:
输入:root = [1] 输出:[1]
提示:
- 树中节点数目在范围
[0, 100]内 -100 <= Node.val <= 100
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
最优解(迭代栈:一路向左入栈,再回退处理右子树)
最优解讲解(通俗版 + 推理过程)
- 中序遍历顺序:左 → 根 → 右。
- 递归很好写,但迭代要自己维护“回来的路”:这条“回来的路”就是栈。
- 推理流程:
- 只要能往左走,就一直把节点压栈(表示“我回来还要处理它和它的右子树”)。
- 走到最左空了,就弹栈:此时左边都处理完了,轮到“根”。
- 处理完根后,再去它的右子树,重复上述过程。
- 不变量:栈里存的是“左子树已经走到底但根还没输出”的节点路径。
- 为什么一定按这个顺序压栈/弹栈?
- 你可以把它当成“手动展开递归”:递归在进入左子树前会把当前节点压到调用栈里,等左子树处理完再回来处理根和右子树;迭代栈就是在模拟同一件事。
- 复杂度:每个节点入栈/出栈各一次,时间 (O(n));栈深度最多树高 (O(h))。
类似题目(DFS 迭代模板:栈模拟递归)
while (cur || stack.length) {while (cur) { stack.push(cur); cur = cur.left }cur = stack.pop()visit(cur)cur = cur.right}
104. 二叉树的最大深度
给定一个二叉树 root ,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
示例 1:

输入:root = [3,9,20,null,null,15,7] 输出:3
示例 2:
输入:root = [1,null,2] 输出:2
提示:
- 树中节点的数量在
[0, 104]区间内。 -100 <= Node.val <= 100
最优解(DFS:深度 = 1 + max(左深, 右深))
最优解讲解(通俗版 + 推理过程)
- 深度的定义:从根到最远叶子的节点数。
- 关键推理:站在一个节点上,它的最大深度就是:
- 如果是空节点:深度 0
- 否则:
1 + max(左子树最大深度, 右子树最大深度)
- 这就是典型的树形递归:把“大问题”拆成两个子问题。
- 小例子:根的左子树深度 2、右子树深度 3,那么整棵树深度就是
1 + max(2,3) = 4。 - 复杂度:每个节点访问一次,时间 (O(n)),递归栈深度 (O(h))。
类似题目(树的高度/深度:1 + max 子树)
if (!node) return 0return 1 + Math.max(dfs(node.left), dfs(node.right))
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
最优解(递归:交换左右子树)
最优解讲解(通俗版 + 推理过程)
- 翻转的定义:每个节点都交换它的左子树和右子树。
- 为什么递归最自然:树的操作通常都是“当前节点做一次操作 + 递归处理子树”。
- 推理顺序:
- 先把左右子树分别翻转(或者先交换再翻转也行)
- 再把它们互换挂回去
- 每个节点恰好访问一次,时间 (O(n))。
- 为什么不会丢节点?:我们只是交换指针指向(
left/right),并没有创建/删除节点;递归会把原本的子树继续翻转完再挂回去。 - 复杂度:时间 (O(n)),递归栈 (O(h))。
类似题目(树结构变换:对每个节点做局部修改)
if (!node) return nullswap(node.left, node.right)dfs(node.left); dfs(node.right)
101. 对称二叉树
给你一个二叉树的根节点 root , 检查它是否轴对称。
示例 1:
输入:root = [1,2,2,3,4,4,3] 输出:true
示例 2:
输入:root = [1,2,2,null,3,null,3] 输出:false
提示:
- 树中节点数目在范围
[1, 1000]内 -100 <= Node.val <= 100
进阶:你可以运用递归和迭代两种方法解决这个问题吗?
最优解(递归判镜像:左的左 vs 右的右)
最优解讲解(通俗版 + 推理过程)
- 对称的本质不是“值相同”这么简单,而是结构和对应位置都要像镜子一样。
- 关键推理:把问题改成“判断两棵树是否互为镜像”:
- 两棵树镜像 ⇔ 根值相同,且:
- 左树的左子树 ⇔ 右树的右子树
- 左树的右子树 ⇔ 右树的左子树
- 两棵树镜像 ⇔ 根值相同,且:
- 用递归对这两对关系做同样检查即可。
- 最常见的坑:只比较“每层值是否对称”是不够的,结构(空节点位置)也必须对称。
- 复杂度:每个节点最多比较一次,时间 (O(n)),递归栈 (O(h))。
类似题目(双树比较:结构 + 值)
if (!a && !b) return trueif (!a || !b) return falsereturn a.val === b.val && dfs(a.left, b.right) && dfs(a.right, b.left)
543. 二叉树的直径
给你一棵二叉树的根节点,返回该树的 直径 。
二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。
两节点之间路径的 长度 由它们之间边数表示。
示例 1:
输入:root = [1,2,3,4,5] 输出:3 解释:3 ,取路径 [4,2,1,3] 或 [5,2,1,3] 的长度。
示例 2:
输入:root = [1,2] 输出:1
提示:
- 树中节点数目在范围
[1, 104]内 -100 <= Node.val <= 100
最优解(DFS 计算高度,顺便更新“经过当前节点的最长路径”)
最优解讲解(通俗版 + 推理过程)
- 直径是什么:任意两点之间最长路径的边数,不一定经过根。
- 朴素想法:枚举每个节点作为路径拐点再算,容易重复计算。
- 关键推理:直径一定是某个节点的“左最高 + 右最高”:
- 如果最长路径经过某节点,那么它从左子树某叶子走到右子树某叶子。
- 这条路径长度就是“左子树高度 + 右子树高度”(按边数)。
- 所以我们写一个求高度的 DFS,同时在每个节点更新一次
best,一趟 DFS 就搞定。 - 为什么能用“高度”来表达?:经过当前节点的最长路径,一定由“左边最深叶子到当前节点” + “右边最深叶子到当前节点”拼起来,而这两段长度就是左右高度。
- 复杂度:后序遍历一次,时间 (O(n)),递归栈 (O(h))。
类似题目(后序 DFS:返回高度/贡献,顺便更新全局答案)
function dfs(node){lh = dfs(node.left); rh = dfs(node.right)best = max(best, combine(lh,rh))return 1 + max(lh,rh)}
102. 二叉树的层序遍历
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例 1:
输入:root = [3,9,20,null,null,15,7] 输出:[[3],[9,20],[15,7]]
示例 2:
输入:root = [1] 输出:[[1]]
示例 3:
输入:root = [] 输出:[]
提示:
- 树中节点数目在范围
[0, 2000]内 -1000 <= Node.val <= 1000
最优解(BFS 队列:按层取 size)
最优解讲解(通俗版 + 推理过程)
- 层序遍历就是 BFS:从根开始,一层一层往下扩散。
- 怎么分层?:在进入一层时,队列里正好存着这一层所有节点,所以先记录
size = q.length,然后弹出size次就是这一层。 - 不变量:每轮循环开始时,队列只包含“当前层”的节点;处理完 size 个后,队列里变成“下一层”的节点。
- 为什么
size必须先存下来?:因为你在处理这一层时会不断push子节点进队列,如果不锁定 size,就会把下一层也混进当前层。 - 复杂度:每个节点进队/出队一次,时间 (O(n)),空间 (O(w))(w 为最大层宽)。
类似题目(按层 BFS 模板)
while (q.length) {size = q.lengthfor i in 1..size: pop + push children}
108. 将有序数组转换为二叉搜索树
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 平衡 二叉搜索树。
示例 1:
输入:nums = [-10,-3,0,5,9] 输出:[0,-3,9,-10,null,5] 解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:![]()
示例 2:
输入:nums = [1,3] 输出:[3,1] 解释:[1,null,3] 和 [3,1] 都是高度平衡二叉搜索树。
提示:
1 <= nums.length <= 104-104 <= nums[i] <= 104nums按 严格递增 顺序排列
最优解(分治:取中点当根,左右递归)
最优解讲解(通俗版 + 推理过程)
- 要构造“高度平衡”的 BST,直觉就是:根尽量选中间,这样左右子树大小接近。
- 因为数组有序,取中点做根后:
- 左边部分都比根小 → 递归建左子树
- 右边部分都比根大 → 递归建右子树
- 递归到区间为空结束。整体时间 (O(n)),每个元素只用一次。
- 为什么中序遍历能验证结果?:BST 的中序遍历一定是升序;我们用它检查“建出来的树仍能还原原数组”,说明结构符合 BST。
- 复杂度:时间 (O(n)),递归栈深度 (O(\log n))(平衡时)。
类似题目(分治构造:中点做根/主元)
root = midleft = build(l, mid-1)right = build(mid+1, r)
98. 验证二叉搜索树
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 严格小于 当前节点的数。
- 节点的右子树只包含 严格大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:root = [2,1,3] 输出:true
示例 2:
输入:root = [5,1,4,null,null,3,6] 输出:false 解释:根节点的值是 5 ,但是右子节点的值是 4 。
提示:
- 树中节点数目范围在
[1, 104]内 -231 <= Node.val <= 231 - 1
最优解(上下界递归:每个节点都要落在 (min, max) 内)
最优解讲解(通俗版 + 推理过程)
- BST 的坑:不是只要
左 < 根 < 右就行,左子树所有节点都要小于根,右子树所有节点都要大于根。 - 关键推理:每个节点都有“允许的取值范围”:
- 根:(-∞, +∞)
- 走到左子树:上界变成当前节点值
- 走到右子树:下界变成当前节点值
- 只要某个节点越界,就不是 BST。
- 为什么不能只检查父子关系?
- 因为右子树里的某个节点可能比根还小(典型反例:根 5,右子树里出现 3),它和父节点可能仍满足局部大小关系,但整体违反 BST。
- 复杂度:时间 (O(n)),递归栈 (O(h))。
类似题目(递归传上下界模板)
dfs(node, low, high):check low < node.val < highdfs(left, low, node.val)dfs(right, node.val, high)
230. 二叉搜索树中第 K 小的元素
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 小的元素(k 从 1 开始计数)。
示例 1:
输入:root = [3,1,4,null,2], k = 1 输出:1
示例 2:
输入:root = [5,3,6,2,4,null,null,1], k = 3 输出:3
提示:
- 树中的节点数为
n。 1 <= k <= n <= 1040 <= Node.val <= 104
进阶:如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化算法?
最优解(BST 中序遍历就是升序:数到第 k 个)
最优解讲解(通俗版 + 推理过程)
- BST 的一个黄金性质:中序遍历(左-根-右)输出的序列一定是从小到大。
- 所以“第 k 小”就变成:做一次中序遍历,数到第 k 个节点即可。
- 用迭代栈可以避免递归深度问题;每弹出一次栈,就相当于“访问到一个新的最小值”。
- 为什么是“弹栈时计数”?
- 因为中序的“访问节点”发生在“左子树处理完之后”,而迭代实现中正对应
cur = stack.pop()那一刻。
- 因为中序的“访问节点”发生在“左子树处理完之后”,而迭代实现中正对应
- 复杂度:最坏要走到第 k 个节点,时间 (O(h+k))(最坏 (O(n))),空间 (O(h))。
类似题目(BST 的顺序统计:中序 + 计数)
inorder: visitCount++; if visitCount==k return node.val
199. 二叉树的右视图
给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
示例 1:
输入:root = [1,2,3,null,5,null,4]
输出:[1,3,4]
解释:

示例 2:
输入:root = [1,2,3,4,null,null,null,5]
输出:[1,3,4,5]
解释:

示例 3:
输入:root = [1,null,3]
输出:[1,3]
示例 4:
输入:root = []
输出:[]
提示:
- 二叉树的节点个数的范围是
[0,100] -100 <= Node.val <= 100
最优解(BFS 按层:每层最后一个就是右视图)
最优解讲解(通俗版 + 推理过程)
- 右视图的含义:从右侧看过去,每一层你能看到的“最右边”那个节点。
- 关键推理:按层遍历时,每层的最后一个节点就是最右边:
- BFS 队列一次弹出一层
size个节点; - 第
size-1个弹出的就是这一层的最右节点(与入队顺序无关,只要同层都处理到即可)。
- BFS 队列一次弹出一层
- 另一种等价做法是 DFS 先走右子树并记录“第一次到达某层”的节点。
- 为什么“最后一个”一定是可见的?:同层节点从左到右排列,右侧视角只能看到最右端的那个(其它都被挡住)。
- 复杂度:时间 (O(n)),空间 (O(w))。
类似题目(按层取特征:每层第一个/最后一个)
for each level:if i==0 take left viewif i==size-1 take right view
114. 二叉树展开为链表
给你二叉树的根结点 root ,请你将它展开为一个单链表:
- 展开后的单链表应该同样使用
TreeNode,其中right子指针指向链表中下一个结点,而左子指针始终为null。 - 展开后的单链表应该与二叉树 先序遍历 顺序相同。
示例 1:
输入:root = [1,2,5,3,4,null,6] 输出:[1,null,2,null,3,null,4,null,5,null,6]
示例 2:
输入:root = [] 输出:[]
示例 3:
输入:root = [0] 输出:[0]
提示:
- 树中结点数在范围
[0, 2000]内 -100 <= Node.val <= 100
进阶:你可以使用原地算法(O(1) 额外空间)展开这棵树吗?
最优解(反向前序:右→左→根,用 prev 串起来)
最优解讲解(通俗版 + 推理过程)
- 目标结构:按前序遍历顺序,把树“拉直”成只用
right指针的链表,且left全部置空。 - 关键推理:如果我们从后往前把节点串起来会更容易:
- 最终链表的“下一个”是谁?就是前序遍历里当前节点的下一个节点。
- 反过来想:如果我们按“前序的逆序”遍历(右→左→根),就能在回溯时用一个
prev指针把链表串起来:- 当前节点的
right指向prev - 更新
prev = 当前节点
- 当前节点的
- 为什么是右→左→根?:前序是 根→左→右,逆过来就是 右→左→根。
- 为什么要把
left置空?:题目要求展开后是“只用 right 指针”的链表,如果 left 不置空就不符合输出结构。 - 复杂度:每个节点访问一次,时间 (O(n)),递归栈 (O(h))。
类似题目(反向遍历 + prev 指针重建链)
dfs(node.right); dfs(node.left)node.right = prev; node.left = null; prev = node
105. 从前序与中序遍历序列构造二叉树
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
示例 1:
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7] 输出: [3,9,20,null,null,15,7]
示例 2:
输入: preorder = [-1], inorder = [-1] 输出: [-1]
提示:
1 <= preorder.length <= 3000inorder.length == preorder.length-3000 <= preorder[i], inorder[i] <= 3000preorder和inorder均 无重复 元素inorder均出现在preorderpreorder保证 为二叉树的前序遍历序列inorder保证 为二叉树的中序遍历序列
最优解(分治:前序定根,中序切左右)
最优解讲解(通俗版 + 推理过程)
- 前序的第一个一定是根:因为前序是 根→左→右。
- 中序里根的位置把左右子树切开:中序是 左→根→右,所以根左边都是左子树,根右边都是右子树。
- 递归构造的推理:
- 取当前前序指针
prePos指向的值作为根 - 在中序中找到根的位置
mid - 递归构造左子树(中序区间
[inL, mid-1]) - 递归构造右子树(中序区间
[mid+1, inR])
- 取当前前序指针
- 为什么要 Map 加速?:不然每次找根在中序的位置都要线性搜索,会退化成 (O(n^2));用 Map 后整体 (O(n))。
- 为什么递归区间长度正确?
- 中序中
mid左边的长度就是左子树节点数;而前序会按“根→左→右”消费节点,所以只要用prePos作为全局游标,递归自然会按正确数量把节点分配给左/右子树。
- 中序中
- 复杂度:每个节点只创建一次,查位置 (O(1)),总时间 (O(n)),递归栈 (O(h))。
类似题目(遍历序列建树:一个序列定根,一个序列切左右)
root = preorder[next]mid = pos[root] in inorderleft = build(inL, mid-1)right = build(mid+1, inR)
437. 路径总和 III
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
示例 1:

输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8 输出:3 解释:和等于 8 的路径有 3 条,如图所示。
示例 2:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22 输出:3
提示:
- 二叉树的节点个数的范围是
[0,1000] -109 <= Node.val <= 109-1000 <= targetSum <= 1000
最优解(前缀和 + 哈希:树上也能用“差为 target”)
最优解讲解(通俗版 + 推理过程)
- 路径定义:从某个节点往下走到某个节点(必须向下),不要求从根开始,也不要求到叶子结束。
- 朴素做法:以每个节点为起点做一次 DFS 累加,会重复很多,最坏 (O(n^2))。
- 关键推理:把树上的“路径和”也写成前缀和差值:
- 在一条从根到当前节点的路径上,设
prefix是到当前节点的累计和。 - 如果某段向下路径和为
target,就意味着:存在一个祖先前缀oldPrefix,使得prefix - oldPrefix = target,也就是oldPrefix = prefix - target。 - 所以到达一个节点时,只要知道“当前路径上有多少个前缀和等于
prefix-target”,就能一次性统计出所有以当前节点为结尾的合法路径数。
- 在一条从根到当前节点的路径上,设
- 为什么要回溯减频次?
freq只应该统计“当前这条根到当前节点路径”上的前缀和。- DFS 走完一个子树回到父节点时,要把子树路径上的前缀和移除,否则会把另一条分支当成同一路径,统计出错误答案。
- 为什么这能覆盖“不从根开始”的路径?
- 因为我们统计的是“当前 prefix 与历史 prefix 的差值”,历史 prefix 可以来自任意祖先节点,所以对应的路径起点也可以是任意节点。
- 复杂度:每个节点进出一次,时间 (O(n)),空间 (O(h))(Map 在一条根到叶路径上的前缀和数量)。
类似题目(树上前缀和模板:进入 +1,离开 -1)
dfs(node, prefix):prefix += node.valans += freq[prefix-target]freq[prefix]++dfs(children)freq[prefix]-- // backtrack
236. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 输出:3 解释:节点5和节点1的最近公共祖先是节点3 。
示例 2:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 输出:5 解释:节点5和节点4的最近公共祖先是节点5 。因为根据定义最近公共祖先节点可以为节点本身。
示例 3:
输入:root = [1,2], p = 1, q = 2 输出:1
提示:
- 树中节点数目在范围
[2, 105]内。 -109 <= Node.val <= 109- 所有
Node.val互不相同。 p != qp和q均存在于给定的二叉树中。
最优解(后序 DFS:左右各返回“是否找到目标”)
最优解讲解(通俗版 + 推理过程)
- 最近公共祖先:离 p、q 最近的那个“同时是它们祖先”的节点。
- 关键推理:后序遍历最合适(先处理子树,再决定当前节点):
- 让 DFS 返回一个布尔值:当前子树里是否包含 p 或 q。
- 当前节点成为 LCA 的三种情况:
- 左子树找到一个,右子树找到一个(分居两侧)
- 当前节点本身是 p/q,且左/右子树里找到另一个
- 一旦满足,就把
ans记录下来。 - 为什么要用后序?
- 因为“当前节点是否是 LCA”取决于左右子树各自是否包含目标节点,必须先知道子树结果再做判断(先子后父)。
- 复杂度:每个节点访问一次,时间 (O(n)),递归栈 (O(h))。
类似题目(后序汇总:子树返回信息,父节点做判断)
left = dfs(left); right = dfs(right); mid = isTarget(node)if ((left&&right) || (mid&&(left||right))) ans=nodereturn left || right || mid
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
最优解(后序 DFS:返回“向上贡献”,同时更新“过当前节点的最优”)
最优解讲解(通俗版 + 推理过程)
- 路径定义:可以从任意节点到任意节点,但必须沿父子连接走;路径可以在某个节点“拐弯”一次(左右各走一段),但不能分叉成三条。
- 两个概念要分清:
- 全局最优路径:可能在任意位置拐弯,所以需要一个全局
best。 - 向上贡献值:返回给父节点时,路径不能左右都拿(否则父节点会分叉),所以只能选“左或右里更大的一边”。
- 全局最优路径:可能在任意位置拐弯,所以需要一个全局
- 关键推理(后序):
- 先算出左右子树给你的“向上贡献”;如果贡献是负数,等于拖后腿,直接当 0(不选)。
- 经过当前节点的最佳路径是:
node.val + left + right(可以两边都拿,表示在这里拐弯)。 - 但返回给父节点只能是一条:
node.val + max(left, right)。
- 为什么贡献要和 0 比较?
- 因为路径可以从任意节点开始,如果某一侧贡献为负,选上它只会让总和变小,不如直接在当前节点“重新开始”。
- 复杂度:时间 (O(n)),递归栈 (O(h))。
类似题目(树形 DP:返回局部贡献,更新全局答案)
gain = node.val + max(0, leftGain, rightGain)best = max(best, node.val + max(0,leftGain) + max(0,rightGain))