深度遍历的艺术:从“直径”到“最大路径和”

0 阅读3分钟

在刷力扣二叉树专题时,你一定会遇到这类题目:它们不要求从根节点开始,而是可以在树的任意位置开始和结束。

这类问题的代表作就是 124. 二叉树中的最大路径和543. 二叉树的直径。今天我们通过这两道题,拆解二叉树 DFS(深度优先搜索)中的一个核心套路。


一、 核心套路:每个节点的“双重身份”

在解决这类路径问题时,递归函数里的每一个节点其实都承担了两个职责:

  1. 对自己负责(更新全局答案) :以我为“拐点”,把左子树提供的最长路径和右子树提供的最长路径连接起来,看看能不能打破当前的纪录。
  2. 对父亲负责(返回贡献值) :告诉我的爸爸,如果从他那里经过我往下面走,哪一条路(左还是右)最长,让他去选择。

二、 力扣 543:二叉树的直径

题目大意:找到树中任意两个节点之间最长的路径长度。

1. 通俗理解

想象你在每一个节点上都挂一把尺子。对于节点 AA 来说,经过它的最长路径长度 = 左子树的深度 + 右子树的深度

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) + 1max(left, right) + root.val
全局更新left + right + 1left + right + root.val