LeetCode 236. 二叉树的最近公共祖先
题目描述


题意分析
输入一棵普通二叉树的根,以及树中的两个节点
p和q,要求返回它们深度最大的那个共同祖先节点本身,而不是它的值。题面里有三条容易被忽略的措辞。第一,它明确规定「一个节点也可以是它自己的祖先」,所以当
p恰好在q的上方时,答案就是p,这不是特例而是正常输出之一。第二,它保证p和q都存在于树中,这条前提允许算法一旦命中就不再往下确认另一个目标是否存在——去掉这条前提,题就变成第 1644 题,写法必须改。第三,返回类型是节点指针而不是整数,说明这是「定位」而非「计算」,一路上不需要累积任何数值。这棵树是普通二叉树,不是搜索树:节点值和位置之间没有任何关系,所以无法通过比较取值来决定往哪边走,只能对两棵子树都做搜索。同时二叉树的节点默认没有父指针,「往上找祖先」这个方向不是天然可行的——结构里只有自上而下的通路,任何做法都得先回答「向上的信息从哪来」这个问题。
需要单独想清楚的边界有三种:
p是q的祖先(或反之),答案是那个祖先自己;p或q就是根,答案必然是根;两个节点分列根的左右子树,答案是根。而如果树只有一个节点,那p和q只能都是它,答案还是它。
解法:后序递归
核心思路
递归函数返回当前子树中找到的
p、q或它们的最近公共祖先。当前节点为空或等于目标节点时直接返回;左右子树都返回非空时,当前节点就是首次汇合点,否则继续向上传递非空结果。
解题步骤
- 当前节点为空、等于
p或等于q时,直接返回当前节点。- 分别递归左右子树,得到两侧的查找结果。
- 两侧都非空,说明
p和q分处两侧,返回当前节点。- 只有一侧非空时返回该侧;两侧都为空时自然返回空。
代码实现
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) {
return root;
}
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if (left != null && right != null) {
return root;
}
return left != null ? left : right;
}
}
func lowestCommonAncestor(root *TreeNode, p *TreeNode, q *TreeNode) *TreeNode {
if root == nil || root == p || root == q {
return root
}
left := lowestCommonAncestor(root.Left, p, q)
right := lowestCommonAncestor(root.Right, p, q)
if left != nil && right != nil {
return root
}
if left != nil {
return left
}
return right
}
复杂度分析
- 时间复杂度:$O(n)$,最坏情况下访问整棵树。
- 空间复杂度:$O(h)$,
h为树高,对应递归栈深度。
关键点总结
- 返回值表示当前子树对目标节点的查找结果,合并发生在后序位置。
- 左右结果都非空时,当前节点才是最近公共祖先。
- 比较的是节点引用,不是节点值。
- 题目保证
p和q都存在于树中,因此命中目标节点后可以直接返回。
易错点总结
- 左侧找到节点后就跳过右侧,会漏掉两个目标分处左右子树的情况。
- 先返回任一非空子树,再判断两侧是否都非空,会错过当前汇合点。
- 用节点值代替节点引用,在允许重复值的变体中会误判。
- 若题目不保证两个目标都存在,不能直接沿用命中即返回的逻辑。
相似题目
| 题目 | 难度 | 考察点 |
|---|---|---|
| 235. 二叉搜索树的最近公共祖先 | 中等 | 有序性让节点值能指路,可以单路下降,不必对两棵子树都递归 |
| 1123. 最深叶节点的最近公共祖先 | 中等 | 目标不是给定的两个节点而是全部最深叶子,返回值要同时携带深度和候选答案 |
| 1644. 二叉树的最近公共祖先 II | 中等 | 去掉「两节点必在树中」的前提,不能命中即返回,必须统计一共找到了几个 |
| 1650. 二叉树的最近公共祖先 III | 中等 | 节点自带父指针,无需建表,问题直接退化成两条向上链求首个交点 |
| 1676. 二叉树的最近公共祖先 IV | 中等 | 目标从两个节点推广为一个节点数组,命中判断换成集合成员测试,汇合逻辑不变 |
| 剑指 Offer 68 - I. 二叉搜索树的最近公共祖先 | 简单 | 与 235 同题换皮,用来单练搜索树上的单路下降写法 |
| 剑指 Offer 68 - II. 二叉树的最近公共祖先 | 简单 | 与本题同题换皮,可原样套用后序返回命中节点的模板 |
| 面试题 04.08. 首个共同祖先 | 中等 | 与本题同题换皮,但描述里不保证两节点一定存在,实现时要留意是否需要额外校验 |