LeetCode 236. 二叉树的最近公共祖先|递归后序

一、题目简介

给定一棵二叉树的根节点 root,以及树中的两个节点 pq,找到它们的最近公共祖先(Lowest Common Ancestor)

最近公共祖先的定义: 对于两个节点 p、q,最近公共祖先是满足「p、q 都是它的子孙节点,且自身深度尽可能大」的节点。

举两个经典例子:

复制代码
        3
       / \
      5   1
     / \ / \
    6  2 0  8
      / \
     7   4
  • 例 1:p=5,q=1 → 最近公共祖先是 3(5 在左子树,1 在右子树,第一次交汇于 3)
  • 例 2:p=5,q=4 → 最近公共祖先是 5(5 本身就是 4 的祖先)

二、核心卡点:递归函数到底返回什么

这道题最容易钻的牛角尖:递归返回的是 true/false 吗? 不是。

递归函数的返回值,是当前子树中找到的「关键节点」,可以把它理解成子树给父节点传递的「信号」。 信号只有四种可能:

  • null:这棵子树里既没找到 p,也没找到 q
  • p:这棵子树里找到了 p
  • q:这棵子树里找到了 q
  • 某个节点:这棵子树里已经找到了 LCA

整个递归的过程,就是自底向上传递信号,直到两个信号第一次交汇的过程。


三、递归终止条件(三种情况直接返回)

复制代码
if (root == null || root == p || root == q) {
    return root;
}

这一行是整道题的基础,拆开理解:

  1. root == null:走到叶子节点下面了,这条路径什么都没找到,返回 null
  2. root == p:当前节点就是 p,直接返回 p 这个信号
  3. root == q:当前节点就是 q,直接返回 q 这个信号

新手高频疑问:找到 p 就直接返回,不用继续往下找 q 了吗? 题目已经保证 p、q 一定存在于树中。当前节点是 p,说明 p 就在这棵子树里;至于 q,要么在 p 的子树中,要么在别的分支,交给上层节点去判断就好。这里只需要把「找到 p」这个信号传上去。


四、核心逻辑:左右递归 + 结果汇总

先分别递归左右子树,拿到两边的信号,再判断当前节点的角色。

复制代码
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);

拿到 leftright 两个信号后,一共三种情况:

情况 1:左右都不为空

复制代码
if (left != null && right != null) {
    return root;
}

含义 :左子树找到了一个目标,右子树找到了另一个目标。 说明 p 和 q 分别在当前节点的左右两侧 ,那么当前节点就是它们第一次交汇的位置,也就是最近公共祖先

情况 2:只有左边不为空

说明两个目标都在左子树里,当前节点肯定不是 LCA,把左子树的结果继续向上传递。

情况 3:只有右边不为空

同理,把右子树的结果继续向上传递。

后两种情况可以合并成一行极简写法:

复制代码
return left != null ? left : right;

两边都为空时,自然返回 null,逻辑自洽。


五、完整 AC 代码

java 复制代码
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        // 终止条件:空节点、找到p、找到q,直接返回
        if (root == null || root == p || root == q) {
            return root;
        }
        
        // 后序遍历:先递归左右子树
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        
        // 左右都找到目标 → 当前节点就是LCA
        if (left != null && right != null) {
            return root;
        }
        
        // 哪边有结果,就把哪边的结果往上传递
        return left != null ? left : right;
    }
}

六、完整例子一步步走

还是用这棵经典树:

复制代码
        3
       / \
      5   1
     / \ / \
    6  2 0  8
      / \
     7   4

例子 1:找 p=5,q=1

  1. 进入节点 3:不是空、不是 p/q,递归左右子树
    • 左子树进入节点 5:root == p,直接返回 5 → left = 5
    • 右子树进入节点 1:root == q,直接返回 1 → right = 1
  2. 回到节点 3:left 和 right 都不为空 → 返回当前节点 3 最终答案:3

例子 2:找 p=5,q=4

  1. 进入节点 3:递归左右子树
    • 左子树进入节点 5:root == p,直接返回 5 → left = 5
    • 右子树进入节点 1:往下递归都没找到 4,最终返回 null → right = null
  2. 回到节点 3:只有 left 不为空 → 返回 left,也就是 5 最终答案:5

关键理解:节点 5 直接返回,并没有往下找 4,但结果依然正确。 因为当「5」这个信号传到上层时,上层判断另一边没有信号,说明两个节点都在左子树,而 5 就是更靠近根的那个,也就是 LCA。


七、本质:这是一道后序遍历题

你会发现代码的执行顺序是: 左子树递归 → 右子树递归 → 处理当前节点

这就是标准的后序遍历(Left-Right-Root)

为什么必须是后序? 因为当前节点要判断自己是不是 LCA,必须先拿到左右子树的完整信息。只有知道左边有没有目标、右边有没有目标,才能确定当前节点的角色。

二叉树题通用规律:只要当前节点的答案依赖左右子树的结果,就用后序递归。


八、新手必踩坑总结

  1. 终止条件漏掉 root == p || root == q 只写 root==null,会导致找到目标节点还继续往下递归,逻辑完全错误。

  2. 以为找到 p 就可以结束整个递归 不行。我们要找的是「交汇点」,必须把信号传到上层去汇总判断,不能提前终止。

  3. 搞反交汇逻辑 左右都不为空的时候,当前 root 就是 LCA,直接返回;不要继续往上传错节点。

  4. 额外处理两边都为空的情况 不需要。return left != null ? left : right 两边都空自然返回 null,逻辑已经覆盖。


九、复杂度分析

  • 时间复杂度:O (n) 最坏情况下需要遍历树中所有节点一次。

  • 空间复杂度:O (h) h 是树的高度,来自递归调用栈。 最坏情况树退化成链表,h=n,空间 O (n); 平衡二叉树 h=logn,空间 O (logn)。


十、面试口述标准回答

我用递归后序遍历来解决这道题。 对于每个节点,先分别在左右子树中寻找 p 和 q。如果当前节点为空,或者当前节点本身就是 p 或 q,就直接返回当前节点。 如果左右子树的递归结果都不为空,说明 p 和 q 分别位于当前节点的左右两侧,那么当前节点就是最近公共祖先。 如果只有一侧的结果不为空,说明目标节点或者已经找到的公共祖先在这一侧,就把这一侧的结果继续向上传递。 时间复杂度 O (n),空间复杂度 O (h),h 是树的高度。


十一、核心总结

这道题的核心可以浓缩成四步:

  1. 空或等于目标 → 直接返回
  2. 先查左子树
  3. 再查右子树
  4. 两边都有返回自己,哪边有返回哪边

本质就是后序递归 + 信号传递:子树把结果传给父节点,父节点汇总后再往上传递,最终两个信号第一次交汇的地方,就是最近公共祖先。

相关推荐
luj_176822 分钟前
大律师考核应重能力与科技素养
c语言·开发语言·c++·经验分享·算法
无定义_27 分钟前
Floyd——Warshall
算法
带多刺的玫瑰1 小时前
Leecode#9刷题之回文数
数据结构·算法
泯泷2 小时前
手搓JSVM第 5 篇:写第一个编译器:从 AST 生成 IR
前端·javascript·算法
泯泷2 小时前
手搓JSVM第 7 篇:控制流:if、while 与 jump
前端·javascript·算法
泯泷2 小时前
手搓JSVM第 6 篇:把 IR 编成字节码:emit 与 label fixup
前端·javascript·算法
泯泷2 小时前
手搓JSVM第 3 篇:从栈式 VM 到寄存器式 VM:为什么我们选择寄存器
前端·javascript·算法
泯泷2 小时前
第 4 篇:让 VM 支持变量:Slot、Environment 与 TDZ
前端·javascript·算法
Asize3 小时前
54. 螺旋矩阵
算法