LeetCode 124/543 树形DP

目录

[一、题目 1:二叉树中的最大路径和(LeetCode 124)](#一、题目 1:二叉树中的最大路径和(LeetCode 124))

[1. 题目描述](#1. 题目描述)

[2. 核心思想(树形 DP)](#2. 核心思想(树形 DP))

[3. 解法实现](#3. 解法实现)

[(1)基础递归解法(时间 O (n),空间 O (h),h 为树高)](#(1)基础递归解法(时间 O (n),空间 O (h),h 为树高))

(2)优化:迭代后序遍历(避免递归栈溢出)

[4. 易错点](#4. 易错点)

[二、题目 2:二叉树的直径(LeetCode 543)](#二、题目 2:二叉树的直径(LeetCode 543))

[1. 题目描述](#1. 题目描述)

[2. 核心思想(树形 DP)](#2. 核心思想(树形 DP))

[3. 解法实现](#3. 解法实现)

[(1)基础递归解法(时间 O (n),空间 O (h))](#(1)基础递归解法(时间 O (n),空间 O (h)))

(2)优化:迭代后序遍历

[4. 易错点](#4. 易错点)

[三、树形 DP 常见题型的核心模板](#三、树形 DP 常见题型的核心模板)

[1. 模板结构](#1. 模板结构)

[2. 通用代码框架](#2. 通用代码框架)

[3. 题型适配示例](#3. 题型适配示例)

[4. 适用场景](#4. 适用场景)


一、题目 1:二叉树中的最大路径和(LeetCode 124)

1. 题目描述

给定二叉树的根节点,返回树中任意路径的节点值之和的最大值

  • 路径定义:节点序列(相邻节点有边、节点不重复),至少含 1 个节点,可不过根。
  • 示例:输入[1,2,3],输出6(路径2→1→3,和为2+1+3)。

2. 核心思想(树形 DP)

通过后序遍历自底向上处理,核心是 "子树贡献→当前节点最优→向上返回贡献":

  1. 子树贡献 :左 / 右子树能提供的最大路径和(若子树贡献为负,取0,相当于 "不选该子树");
  2. 当前节点最优:以当前节点为核心的路径和(左贡献 + 右贡献 + 当前节点值),用它更新全局最大和;
  3. 向上返回贡献:当前节点能向父节点提供的最大路径和(当前节点值 + 左 / 右贡献的较大值)------ 因为父节点的路径只能延伸一个分支。

3. 解法实现

(1)基础递归解法(时间 O (n),空间 O (h),h 为树高)
java 复制代码
class Solution {
    // 全局变量存最大路径和(初始为最小值,处理全负节点的情况)
    private int maxSum = Integer.MIN_VALUE;

    public int maxPathSum(TreeNode root) {
        dfs(root);
        return maxSum;
    }

    // 递归函数:返回当前节点向父节点提供的最大贡献
    private int dfs(TreeNode node) {
        if (node == null) return 0; // 空节点贡献为0
        
        // 左/右子树的贡献(负贡献取0)
        int leftContribution = Math.max(dfs(node.left), 0);
        int rightContribution = Math.max(dfs(node.right), 0);
        
        // 计算当前节点的最优路径和,更新全局最大值
        int currentMax = leftContribution + rightContribution + node.val;
        maxSum = Math.max(maxSum, currentMax);
        
        // 向父节点返回的贡献(只能选一个分支)
        return node.val + Math.max(leftContribution, rightContribution);
    }
}
(2)优化:迭代后序遍历(避免递归栈溢出)

当树深度极大(如 1e5)时,递归会栈溢出,用迭代模拟后序遍历:

java 复制代码
class Solution {
    public int maxPathSum(TreeNode root) {
        if (root == null) return 0;
        int maxSum = Integer.MIN_VALUE;
        Deque<TreeNode> stack = new LinkedList<>();
        // 存储每个节点的贡献(避免重复计算)
        Map<TreeNode, Integer> contributionMap = new HashMap<>();
        
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode node = stack.peek();
            // 先处理左子树(未处理过则压栈)
            if (node.left != null && !contributionMap.containsKey(node.left)) {
                stack.push(node.left);
            }
            // 再处理右子树(未处理过则压栈)
            else if (node.right != null && !contributionMap.containsKey(node.right)) {
                stack.push(node.right);
            }
            // 左右子树都处理完,计算当前节点的贡献
            else {
                stack.pop();
                int left = node.left == null ? 0 : Math.max(contributionMap.get(node.left), 0);
                int right = node.right == null ? 0 : Math.max(contributionMap.get(node.right), 0);
                
                // 更新全局最大和
                maxSum = Math.max(maxSum, left + right + node.val);
                // 存储当前节点的贡献
                contributionMap.put(node, node.val + Math.max(left, right));
            }
        }
        return maxSum;
    }
}

4. 易错点

  • 全局变量初始化为0:若树全是负节点(如[-3,-2,-1]),会错误返回0,需初始化为Integer.MIN_VALUE
  • 返回贡献时包含左右分支:向父节点返回的贡献必须是 "当前节点 + 单个子树贡献",否则父节点无法延伸路径。

二、题目 2:二叉树的直径(LeetCode 543)

1. 题目描述

给定二叉树的根节点,返回树中任意两节点间路径的边数的最大值

  • 路径可不过根,边数 = 路径节点数 - 1;
  • 示例:输入[1,2,3,4,5],输出3(路径4→2→54→2→1→3,边数为 3)。

2. 核心思想(树形 DP)

同样是后序遍历,核心逻辑:

  1. 子树信息:左 / 右子树的深度(深度 = 子树的节点数);
  2. 当前节点最优:以当前节点为核心的路径边数(左深度 + 右深度),用它更新全局最大直径;
  3. 向上返回信息:当前节点的深度(max (左深度,右深度) + 1)------ 父节点的深度依赖当前节点的深度。

3. 解法实现

(1)基础递归解法(时间 O (n),空间 O (h))
java 复制代码
class Solution {
    // 全局变量存最大直径(边数,初始为0)
    private int maxDiameter = 0;

    public int diameterOfBinaryTree(TreeNode root) {
        calcDepth(root);
        return maxDiameter;
    }

    // 递归函数:返回当前节点的深度(节点数)
    private int calcDepth(TreeNode node) {
        if (node == null) return 0; // 空节点深度为0
        
        int leftDepth = calcDepth(node.left);
        int rightDepth = calcDepth(node.right);
        
        // 计算当前节点的路径边数,更新全局直径
        int currentDiameter = leftDepth + rightDepth;
        maxDiameter = Math.max(maxDiameter, currentDiameter);
        
        // 向父节点返回当前节点的深度
        return Math.max(leftDepth, rightDepth) + 1;
    }
}
(2)优化:迭代后序遍历
java 复制代码
class Solution {
    public int diameterOfBinaryTree(TreeNode root) {
        if (root == null) return 0;
        int maxDiameter = 0;
        Deque<TreeNode> stack = new LinkedList<>();
        // 存储每个节点的深度
        Map<TreeNode, Integer> depthMap = new HashMap<>();
        
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode node = stack.peek();
            if (node.left != null && !depthMap.containsKey(node.left)) {
                stack.push(node.left);
            } else if (node.right != null && !depthMap.containsKey(node.right)) {
                stack.push(node.right);
            } else {
                stack.pop();
                int left = node.left == null ? 0 : depthMap.get(node.left);
                int right = node.right == null ? 0 : depthMap.get(node.right);
                
                // 更新全局直径
                maxDiameter = Math.max(maxDiameter, left + right);
                // 存储当前节点的深度
                depthMap.put(node, Math.max(left, right) + 1);
            }
        }
        return maxDiameter;
    }
}

4. 易错点

  • 混淆 "节点数" 和 "边数":直径是边数,等于 "左深度 + 右深度"(深度是节点数),不要多 + 1;
  • 全局变量初始化为 1:边数的最小值是 0(单个节点的情况),需初始化为 0。

三、树形 DP 常见题型的核心模板

树形 DP 的核心是 **"后序遍历 + 子树信息传递 + 全局最优更新"**,以下是通用模板:

1. 模板结构

  1. 定义全局变量:存储题目要求的 "全局最优解"(如最大路径和、最大直径);
  2. 递归函数(后序遍历)
    • 处理空节点的边界条件;
    • 递归遍历左、右子树,获取子树的 "局部信息";
    • 计算 "以当前节点为核心的局部最优解",更新全局变量;
    • 返回 "父节点需要的信息"(子树传递给父节点的信息)。

2. 通用代码框架

java 复制代码
class Solution {
    // 1. 定义全局变量存全局最优解
    private [类型] globalResult = [初始值];

    public [类型] solve(TreeNode root) {
        // 2. 调用后序遍历的递归函数
        postOrder(root);
        return globalResult;
    }

    // 递归函数:返回父节点需要的信息
    private [子树信息类型] postOrder(TreeNode node) {
        // 3. 空节点的边界条件
        if (node == null) return [空节点的信息值];
        
        // 4. 递归处理左、右子树,获取子树信息
        [子树信息类型] leftInfo = postOrder(node.left);
        [子树信息类型] rightInfo = postOrder(node.right);
        
        // 5. 计算当前节点的局部最优解,更新全局结果
        [局部最优解] currentOpt = 计算逻辑(leftInfo, rightInfo, node);
        globalResult = Math.max(globalResult, currentOpt);
        
        // 6. 返回父节点需要的信息
        return 父节点需要的信息逻辑(leftInfo, rightInfo, node);
    }
}

3. 题型适配示例

将模板对应到前两道题:

题型 全局变量类型 / 初始值 子树信息类型 / 空节点值 局部最优解计算逻辑 父节点需要的信息逻辑
最大路径和 int/Integer.MIN_VALUE int/0 leftInfo + rightInfo + node.val node.val + Math.max(leftInfo, rightInfo)
二叉树的直径 int/0 int/0 leftInfo + rightInfo Math.max(leftInfo, rightInfo) + 1

4. 适用场景

该模板适用于所有 "树的路径 / 子树相关最优解" 问题,比如:

  • 最长同值路径(LeetCode 687);
  • 二叉树中的最长交错路径(LeetCode 1372);
  • 打家劫舍 III(LeetCode 337)。

相关推荐
Sheep Shaun2 小时前
STL:list,stack和queue
数据结构·c++·算法·链表·list
杜子不疼.2 小时前
【LeetCode 153 & 173_二分查找】寻找旋转排序数组中的最小值 & 缺失的数字
算法·leetcode·职场和发展
CSDN_RTKLIB2 小时前
【LeetCode 热题 HOT 100】两数之和
算法·leetcode·职场和发展
Tisfy2 小时前
LeetCode 2054.两个最好的不重叠活动:二分查找
算法·leetcode·二分查找·题解
Looooking2 小时前
Python 之通过一个天平找出9个小球中唯一重量较轻的小球
python·算法
white-persist2 小时前
【攻防世界】reverse | tt3441810 详细题解 WP
java·c语言·开发语言·数据结构·c++·算法·安全
YGGP2 小时前
【Golang】LeetCode 70. 爬楼梯
算法·leetcode
小熳芋2 小时前
组合总和- python-回溯哦&剪枝
算法·机器学习·剪枝