【算法3】二叉树中的最大路径和

二叉树中的最大路径和

给定一棵非空二叉树,路径由相邻节点组成,每个节点最多经过一次。路径不必经过根节点,也不必从叶子节点开始或结束。

例如,在下面的二叉树中,最大路径是 15 → 20 → 7,最大路径和是 15 + 20 + 7 = 42

text 复制代码
        -10
        /  \
       9    20
           /  \
          15   7

一、解题思路

当前节点必须先得到左右子树的计算结果,所以采用"左子树、右子树、当前节点"的后序遍历。

递归函数 maxGain(root) 的返回值不是整棵树的最大路径和,而是"从当前节点出发,只向一侧延伸时,能够返回给父节点的最大路径和"。

对于当前节点,左、右子树的贡献分别为 leftGainrightGain。如果某个贡献是负数,连接这棵子树只会使路径和变小,因此通过 Math.max(0, 子树贡献) 将它舍弃。

一条路径如果在当前节点处结束,可以同时连接左右两侧,所以用 root.val + leftGain + rightGain 更新全局变量 maxSum

但当前节点向父节点返回时,路径还要继续向上连接,不能同时保留左右两侧,否则路径会产生分叉。因此只能返回 root.val + Math.max(leftGain, rightGain)

以节点 20 为例,左右子树分别返回 157。经过节点 20 的完整路径和是 15 + 20 + 7 = 42,所以用 42 更新 maxSum;而节点 20 返回给父节点时,只能选择较大的一侧,因此返回 20 + max(15, 7) = 35

两种结果的职责不同:

text 复制代码
root.val + leftGain + rightGain
用于更新整棵树的最大路径和

root.val + max(leftGain, rightGain)
用于返回给父节点继续计算

全局变量 maxSum 必须初始化为 Integer.MIN_VALUE。如果整棵树都是负数,最大路径和应当是其中最大的节点值,而不是 0

二、完整代码

java 复制代码
public class BinaryTreeMaximumPathSum {

    static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;

        TreeNode(int val) {
            this.val = val;
        }
    }

    /**
     * 遍历过程中发现的最大完整路径和。
     */
    private int maxSum;

    public int maxPathSum(TreeNode root) {
        maxSum = Integer.MIN_VALUE;

        // 启动后序遍历,递归过程中会更新 maxSum。
        maxGain(root);

        return maxSum;
    }

    /**
     * 返回从当前节点出发,只向一侧延伸时,
     * 能够提供给父节点的最大路径和。
     */
    private int maxGain(TreeNode root) {
        if (root == null) {
            return 0;
        }

        int leftGain = Math.max(
                0,
                maxGain(root.left)
        );

        int rightGain = Math.max(
                0,
                maxGain(root.right)
        );

        // 完整路径可以同时连接左右两侧。
        int currentPathSum =
                root.val + leftGain + rightGain;

        maxSum = Math.max(
                maxSum,
                currentPathSum
        );

        // 返回父节点时只能选择一侧。
        return root.val + Math.max(
                leftGain,
                rightGain
        );
    }

    public static void main(String[] args) {
        /*
         *          -10
         *          /  \
         *         9    20
         *             /  \
         *            15   7
         */
        TreeNode root = new TreeNode(-10);

        root.left = new TreeNode(9);
        root.right = new TreeNode(20);
        root.right.left = new TreeNode(15);
        root.right.right = new TreeNode(7);

        BinaryTreeMaximumPathSum solution =
                new BinaryTreeMaximumPathSum();

        int result = solution.maxPathSum(root);

        System.out.println("最大路径和:" + result);
    }
}

程序输出:

text 复制代码
最大路径和:42

每个节点只计算一次,所以时间复杂度为 O(n);递归调用栈的最大深度等于树高,所以空间复杂度为 O(h)

三、压栈、出栈与返回父节点

程序首先进入 maxPathSum(-10),将 maxSum 初始化为最小整数,然后调用 maxGain(-10)。此时 maxPathSum 暂停,等待整棵树递归结束。

text 复制代码
栈顶
┌──────────────────────────┐
│ maxGain(-10)              │
├──────────────────────────┤
│ maxPathSum:等待递归结束  │
└──────────────────────────┘
栈底

maxGain(-10) 先计算左子树,因此调用 maxGain(9)。节点 -10 的栈帧不会消失,而是留在栈中等待左子树返回。

text 复制代码
栈顶
┌──────────────────────────┐
│ maxGain(9)                │
├──────────────────────────┤
│ maxGain(-10):等待左结果  │
├──────────────────────────┤
│ maxPathSum:等待递归结束  │
└──────────────────────────┘
栈底

节点 9 的左右子树都是空节点,所以 leftGain = 0rightGain = 0。经过节点 9 的完整路径和为 9 + 0 + 0 = 9,因此将 maxSum 更新为 9;随后,节点 9 向父节点 -10 返回 9 + max(0, 0) = 9,并从调用栈中弹出。

text 复制代码
栈顶
┌──────────────────────────┐
│ maxGain(-10)              │
│ leftGain = 9              │
├──────────────────────────┤
│ maxPathSum:等待递归结束  │
└──────────────────────────┘
栈底

maxSum = 9

得到左子树结果后,maxGain(-10) 调用 maxGain(20) 计算右子树。节点 20 又先调用 maxGain(15),因此栈中依次保存根节点和正在等待结果的父节点。

text 复制代码
栈顶
┌──────────────────────────┐
│ maxGain(15)               │
├──────────────────────────┤
│ maxGain(20):等待左结果   │
├──────────────────────────┤
│ maxGain(-10):等待右结果  │
├──────────────────────────┤
│ maxPathSum:等待递归结束  │
└──────────────────────────┘
栈底

节点 15 没有孩子,所以经过它的完整路径和是 15maxSum9 更新为 15。节点 15 向父节点 20 返回 15,然后出栈。

回到节点 20 后,leftGain = 15。接着调用 maxGain(7) 计算右子树:

text 复制代码
栈顶
┌──────────────────────────┐
│ maxGain(7)                │
├──────────────────────────┤
│ maxGain(20):等待右结果   │
├──────────────────────────┤
│ maxGain(-10):等待右结果  │
├──────────────────────────┤
│ maxPathSum:等待递归结束  │
└──────────────────────────┘
栈底

节点 7 没有孩子,所以向父节点 20 返回 7,然后出栈。此时节点 20 已经得到 leftGain = 15rightGain = 7

text 复制代码
栈顶
┌──────────────────────────┐
│ maxGain(20)               │
│ leftGain = 15             │
│ rightGain = 7             │
├──────────────────────────┤
│ maxGain(-10):等待右结果  │
├──────────────────────────┤
│ maxPathSum:等待递归结束  │
└──────────────────────────┘
栈底

经过节点 20 的完整路径和是 15 + 20 + 7 = 42,所以 maxSum 更新为 42。节点 20 向父节点 -10 返回时只能选择一侧,因此返回 20 + max(15, 7) = 35,然后出栈。

回到根节点 -10 后,左右子树的返回值分别是 935

text 复制代码
栈顶
┌──────────────────────────┐
│ maxGain(-10)              │
│ leftGain = 9              │
│ rightGain = 35            │
├──────────────────────────┤
│ maxPathSum:等待递归结束  │
└──────────────────────────┘
栈底

maxSum = 42

经过根节点的完整路径和是 9 + (-10) + 35 = 34,小于当前的 42,所以 maxSum 不变。

根节点还会计算单边返回值 -10 + max(9, 35) = 25,但根节点没有父节点,所以这个返回值没有被使用。maxGain(-10) 出栈后,程序回到 maxPathSum

text 复制代码
栈顶
┌──────────────────────────┐
│ maxPathSum                │
│ maxSum = 42               │
└──────────────────────────┘
栈底

最后,maxPathSum 返回 maxSum,结果为 42

整个过程只有一条数据链:

text 复制代码
子节点返回单边贡献
        ↓
父节点组合左右贡献
        ↓
更新全局最大路径和
        ↓
父节点再返回自己的单边贡献

maxGain 的返回值供父节点继续计算,maxSum 记录遍历过程中出现的最大完整路径,maxPathSum 在递归结束后返回最终结果。

相关推荐
XUEYUAN52121 小时前
IP 池调度算法实战:代理池负载均衡、健康检查与失效剔除机制(2026)
tcp/ip·算法·负载均衡
无小道1 小时前
算法——问题转化
算法
evans在进步1 小时前
LeetCode 53 最大子数组和:一次遍历掌握 Kadane 算法
算法·leetcode·职场和发展
小七在进步1 小时前
数据结构:选择排序
数据结构·算法·排序算法
Nil2082 小时前
leetcode 230二叉搜索树中第k小的元素
算法·leetcode·职场和发展
被怪兽吃掉了2 小时前
5.2.1一维组数定义方式
开发语言·c++·算法
不会就选b2 小时前
Linux之线程进阶---封装信号量
数据结构·算法
不会就选b2 小时前
算法日常・每日刷题--<BFS&&最短路径>4
算法·宽度优先
旖旎夜光2 小时前
LeetCode 69:x 的平方根(二分查找) —— 题解
数据结构·c++·算法·leetcode·二分查找