代码随想录算法训练营第15天|二叉树Part03
记录题目:
- 路径总和
- 路径总和 II
- 二叉树的最近公共祖先
如何判断递归函数是否需要返回值?
如果要搜索其中一条符合条件的路径,那么递归一定需要返回值, 因为遇到符合条件的路径了就要及时返回,通常可以用布尔类型表示。
如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。
如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。
叶子节点 是指没有子节点的节点。
返回值为布尔类型:
1 | /** |
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
无返回值:
1 | var pathsum = function(root, targetsum) { |
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
返回值为目标节点:
1 | var lowestCommonAncestor = function(root, p, q) { |
太好了,你已经更新了代码部分,下面我将按照我上面建议的风格继续修改你的文档内容,保持统一、专业、清晰,并删除冗余,精炼结构。
给定一个二叉树,找到该树中两个指定节点的最近公共祖先。
根据百度百科的定义: “对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(即离两者最近)。”
解法:递归 + 后序遍历
1 | var lowestCommonAncestor = function(root, p, q) { |
思路分析
该算法使用了后序遍历(即“左右中”的遍历顺序),核心思想是:
从底向上回溯,判断当前节点是否为
p
和q
的最近公共祖先。
为什么可以不显式遍历两个节点?
如果 p
是 q
的祖先,递归在访问到 p
时就会返回 p
,而不会继续显式地遍历 q
。
但这并不会影响正确性。因为:
- 我们关心的是:当前节点的左子树是否包含一个目标节点,右子树是否包含另一个;
- 如果只在一个子树中找到了一个非空结果,就继续返回那个节点;
- 如果两边都找到了非空结果,说明当前节点正好是两者交汇点,即最近公共祖先。
换句话说,返回值的传递就是信息的传播路径,而不是访问状态的标记。
为什么用后序遍历?
这个问题的本质是从子树结果中“归纳”出当前节点是否满足祖先条件,这是后序遍历的典型应用场景。
后序遍历的结构:先处理左子树 → 再处理右子树 → 再处理当前节点
这样的遍历方式可以确保:
- 当前节点能拿到左右子树的返回信息;
- 决策当前节点是否是“最近公共祖先”时,信息已就绪;
- 实现“从叶子节点向根汇总”的逻辑判断。
总结:后序遍历适用场景
后序遍历适合处理以下类型问题:
问题类型 | 是否适用 | 理由 |
---|---|---|
当前节点决策依赖子树结果 | ✔ | 需要知道左右子树是否含有目标节点后,才能判断当前是否是祖先 |
子树结果需要合并 | ✔ | 如计算树高、路径和等,左右结果参与当前计算 |
只需遍历所有节点 | ✘ | 如遍历打印、序列化等,前序/中序遍历更直接简洁 |