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

二叉树中的最大路径和

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

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

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

一、解题思路

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

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

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

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

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

以节点 20 为例,左右子树分别返回 15 和 7。经过节点 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 = 0、rightGain = 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 没有孩子,所以经过它的完整路径和是 15,maxSum 从 9 更新为 15。节点 15 向父节点 20 返回 15,然后出栈。

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

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

节点 7 没有孩子,所以向父节点 20 返回 7,然后出栈。此时节点 20 已经得到 leftGain = 15 和 rightGain = 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 后,左右子树的返回值分别是 9 和 35。

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 在递归结束后返回最终结果。

相关推荐
Code_Solitude2 小时前
C语言动态内存 + 柔性数组 + 数组名退化
c语言·算法·柔性数组
上海潭龙东海科技有限公司2 小时前
客运站管控之动态孪生技术方案(新增一屏管控模块
数据仓库·人工智能·算法·重构·动态规划
YLCHUP4 小时前
【思维&图论建模】题解:债权(debt)_图论_建模_思路转化_思维题目_C++算法竞赛
c++·笔记·算法·数学建模·贪心算法·深度优先·图论
思麟呀5 小时前
背包DP(01背包+完全背包)
c++·算法
笑容掩饰我的失落6 小时前
区块链从小白到精通(七):Solidity 语法精讲——数据类型、函数、修饰器与事件
算法·区块链·php·智能合约·哈希算法
Pniubi7 小时前
力扣24. 两两交换链表中的节点(递归)
算法·leetcode·链表
꧁细听勿语情꧂7 小时前
直接选择排序,堆排序,冒泡排序,递归快速排序、hoare版本、挖坑法、lomuto前后指针,非递归快速排序
c语言·开发语言·数据结构·算法
俊俊几年的8 小时前
动态规划(Dynamic Programming)详解:从入门到 LeetCode Hot 100
linux·c++·算法·leetcode·嵌入式·动态规划·c
小雉系统17 小时前
BcoreOS安全通信架构:FsSession 抗算法后门安全设计
算法·安全·架构