在刷力扣二叉树专题时,你一定会遇到这类题目:它们不要求从根节点开始,而是可以在树的任意位置开始和结束。
这类问题的代表作就是 124. 二叉树中的最大路径和 和 543. 二叉树的直径。今天我们通过这两道题,拆解二叉树 DFS(深度优先搜索)中的一个核心套路。
一、 核心套路:每个节点的“双重身份”
在解决这类路径问题时,递归函数里的每一个节点其实都承担了两个职责:
- 对自己负责(更新全局答案) :以我为“拐点”,把左子树提供的最长路径和右子树提供的最长路径连接起来,看看能不能打破当前的纪录。
- 对父亲负责(返回贡献值) :告诉我的爸爸,如果从他那里经过我往下面走,哪一条路(左还是右)最长,让他去选择。
二、 力扣 543:二叉树的直径
题目大意:找到树中任意两个节点之间最长的路径长度。
1. 通俗理解
想象你在每一个节点上都挂一把尺子。对于节点 来说,经过它的最长路径长度 = 左子树的深度 + 右子树的深度。
2. 代码实现(你的代码)
你的代码采用了“计算节点数”的方法:ans 记录的是路径上的节点总数,所以最后结果要 -1(因为路径长度 = 节点数 - 1)。
Java
class Solution {
private int ans = 0;
public int diameterOfBinaryTree(TreeNode root) {
Dfs(root);
return ans - 1; // 边长 = 节点数 - 1
}
private int Dfs(TreeNode root) {
if(root == null) return 0;
int left = Dfs(root.left); // 左边能提供的最长节点数
int right = Dfs(root.right); // 右边能提供的最长节点数
// 【身份1:更新全局答案】
// 以当前节点为拐点,左+右+自己,更新最大值
int node = left + right + 1;
ans = Math.max(ans, node);
// 【身份2:对父亲负责】
// 只能给父亲选一条路:要么走左边,要么走右边,然后加上自己
return Math.max(left, right) + 1;
}
}
三、 力扣 124:二叉树中的最大路径和
题目大意:每个节点都有一个权值(可能是负数),求路径权值之和的最大值。
1. 通俗理解
这题是 543 的进阶版。区别在于:
- 543 算的是“长度”(个数),肯定越长越好。
- 124 算的是“权值和”,如果子树提供的全是负数,我们宁愿舍弃该子树(即贡献值为 0)。
2. 逻辑实现
你会发现,代码结构和 543 惊人地相似:
Java
class Solution {
private int maxSum = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
dfs(root);
return maxSum;
}
private int dfs(TreeNode root) {
if (root == null) return 0;
// 如果子树贡献是负数,我们直接取 0 (表示不选这条路)
int left = Math.max(0, dfs(root.left));
int right = Math.max(0, dfs(root.right));
// 【身份1:更新全局答案】
// 以当前节点为拐点,把左、右、自己全连起来
maxSum = Math.max(maxSum, left + right + root.val);
// 【身份2:对父亲负责】
// 告诉父亲,走我这条路能拿到的最大收益(选左或者选右,加上我自己)
return root.val + Math.max(left, right);
}
}
四、 总结:两道题的异同点
| 维度 | 543. 二叉树的直径 | 124. 最大路径和 |
|---|---|---|
| 关注点 | 路径的长度(节点/边数) | 路径节点的数值之和 |
| 子树贡献 | 总是正数,必须向上返回 | 如果是负数,可以舍弃(取 0) |
| 递归返回值 | max(left, right) + 1 | max(left, right) + root.val |
| 全局更新 | left + right + 1 | left + right + root.val |