当前位置: 首页 > news >正文

【LeetCode 热题 100】124. 二叉树中的最大路径和——DFS

Problem: 124. 二叉树中的最大路径和
二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其 最大路径和 。

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(N)
    • 空间复杂度:O(H)

整体思路

这段代码旨在解决一个高难度的二叉树问题:二叉树中的最大路径和 (Binary Tree Maximum Path Sum)。这里的“路径”被定义为从树中任意节点到任意节点的序列,路径中至少包含一个节点,且不一定需要经过根节点。

该算法采用了一种精巧的 后序遍历 (Post-order Traversal) 递归思想。算法的核心在于,dfs 递归函数承担了双重职责

  1. 职责一:更新全局最大路径和

    • 在每次访问一个节点 node 时,它都将该节点视为一个潜在的“路径转折点”或“拱顶”。
    • node 为转折点的路径,其最大和可以由 node.val 加上从左子树贡献的最大“向下”路径和 left,再加上从右子树贡献的最大“向下”路径和 right 构成。即 sum = node.val + left + right
    • 这个 sum 值代表了一个完整的、可能横跨左右子树的路径,它是一个全局最大路径和的候选者。因此,代码用 ans = Math.max(ans, sum) 来随时更新全局最大值 ans
  2. 职责二:向父节点贡献“单边”路径

    • dfs(node) 完成计算后,它需要返回一个值给它的父节点。这个返回值不能是上面计算的“拱形”路径和 sum,因为一条路径不能分叉。
    • 它必须返回一个node 出发,一路向下的“单边”路径的最大和。这个单边路径要么走向左子树,要么走向右子树。
    • 因此,它计算 Math.max(left, right) + node.val,选择左右两条单边路径中较大的一条,并加上当前节点的值。
    • 关键的剪枝:如果这个计算出的最佳“单边”路径和是负数,那么它对父节点来说只会起到负面作用。在这种情况下,父节点还不如不包含这条路径。因此,函数最终返回 Math.max(..., 0),即如果贡献为负,则贡献一个0。

通过这种后序遍历,每个节点都能利用其子节点计算出的“单边最大贡献”,来计算以自身为“拱顶”的全局路径和,并同时计算出能向上贡献给父节点的新的“单边最大贡献”。

完整代码

class Solution {// ans: 全局变量,用于存储和更新在整个树中找到的最大路径和。// 初始化为整数的最小值,以确保任何路径和(包括负数)都能正确地更新它。private int ans = Integer.MIN_VALUE;/*** 主函数:启动递归计算并返回最终的最大路径和。* @param root 树的根节点* @return 树中的最大路径和*/public int maxPathSum(TreeNode root) {// 调用递归辅助函数,该函数会通过副作用(修改 ans)来计算结果。dfs(root);// 返回全局最大值。return ans;}/*** 递归辅助函数 (DFS)。* 该函数有两个职责:* 1. 计算以 `node` 为“转折点”的最大路径和,并用它更新全局 `ans`。* 2. 返回从 `node` 出发向下的“单边”路径的最大和,作为对父节点的贡献。* @param node 当前访问的节点* @return 从 `node` 出发的单边路径的最大贡献值(非负)。*/private int dfs(TreeNode node) {// 递归的终止条件:如果节点为空,其贡献的路径和为0。if (node == null) {return 0;}// 后序遍历:先递归计算左右子树的贡献。// left: 从左子节点出发的单边路径的最大贡献值(已经过 max(..., 0) 处理,非负)。int left = dfs(node.left);// right: 从右子节点出发的单边路径的最大贡献值(非负)。int right = dfs(node.right);// 【职责一】计算以当前 node 为“拱顶”的路径和。// 这条路径连接了左、右子树贡献的最大单边路径。int sum = left + right + node.val;// 用这个“拱形”路径和去更新全局最大路径和 ans。ans = Math.max(ans, sum);// 【职责二】计算并返回对父节点的“单边”贡献。// 路径不能分叉,所以只能选择左或右中贡献更大的那一条。// Math.max(left, right) + node.val 表示从 node 向下的最大单边路径和。// 再次与 0 比较,如果这条最佳单边路径的和是负数,就“剪掉”它,向上贡献 0。return Math.max(Math.max(left, right) + node.val, 0);}
}

时空复杂度

时间复杂度:O(N)

  1. 遍历:该算法的核心是深度优先搜索(DFS),它以后序遍历的方式访问树中的每一个节点。
  2. 访问次数:每个节点都会被访问且仅被访问一次。
  3. 节点操作:在每个节点的访问过程中,执行的都是常数时间的算术运算和 Math.max 操作。
  4. 综合分析
    • 算法由 N 次 O(1) 的操作组成,其中 N 是树中的节点数。
    • 因此,总的时间复杂度是 O(N)

空间复杂度:O(H)

  1. 主要存储开销:该算法的空间开销主要来自于递归调用栈
  2. 递归深度:调用栈的最大深度取决于树的高度 H
    • 最坏情况:如果树退化成一个链表(Skewed Tree),树的高度 H 等于节点数 N。此时,空间复杂度为 O(N)
    • 平均/最好情况:如果树是平衡的(Balanced Tree),树的高度 H 约等于 log N。此时,空间复杂度为 O(log N)

综合分析
算法的额外辅助空间复杂度由递归栈的深度决定,因此可以统一表示为 O(H),其中 H 是树的高度。

http://www.lryc.cn/news/594307.html

相关文章:

  • 网络安全隔离技术解析:从网闸到光闸的进化之路
  • 【机器学习深度学习】魔塔社区模型后缀全解析:Base、Chat、Instruct、Bit、Distill背后的技术密码
  • leetcode丑数II计算第n个丑数
  • Java行为型模式---解释器模式
  • 大语言模型:人像摄影的“达芬奇转世”?——从算法解析到光影重塑的智能摄影革命
  • 核电子数字多道分析(DMCA)系统中,脉冲展宽的核心目的
  • 力扣:动态规划java
  • 基于单片机的火灾报警系统设计
  • 每日算法刷题Day50:7.20:leetcode 栈8道题,用时2h30min
  • 处理Electron Builder 创建新进程错误 spawn ENOMEM
  • C++ primer知识点总结
  • D. Traffic Lights 【Codeforces Round 1038, Div. 1 + Div. 2】
  • docker制作前端镜像
  • securecrt连接服务器报错 Key exchange failed 怎么办
  • Direct3D 11学习(一)
  • 股票账户数据及其数据获取
  • Python dataclass 高阶用法与技巧
  • ADC和DMA简述
  • Java中List<int[]>()和List<int[]>[]的区别
  • k8s:离线添加集群节点
  • MySQL—表设计和聚合函数以及正则表达式
  • 【性能测试】性能压测3个阶段+高频面试题回答(详细)
  • 第三章自定义检视面板_创建自定义编辑器类_编辑器操作的撤销与恢复(本章进度3/9)
  • Android 项目中如何在执行 assemble 或 Run 前自动执行 clean 操作?
  • Milvus Dify 学习笔记
  • Unity学习笔记(五)——3DRPG游戏(2)
  • 正点原子stm32F407学习笔记10——输入捕获实验
  • 【no vue no bug】 npm : 无法加载文件 D:\software\nodeJS\node22\npm.ps1
  • ansible awx自动化工具学习准备
  • [学习] 深入理解傅里叶变换:从时域到频域的桥梁