LeetCode 第 236 题「二叉树的最近公共祖先」
字数 1723 2025-10-25 19:08:54
好的,我们这次来详细讲解 LeetCode 第 236 题「二叉树的最近公共祖先」。
1. 题目描述
题目链接:https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/
题目:
给定一个二叉树,以及该二叉树中的两个节点 p 和 q,要求找到它们的最近公共祖先(Lowest Common Ancestor, LCA)。
定义:
最近公共祖先是指同时为节点 p 和 q 的祖先,且在深度上尽可能深的节点(一个节点可以是自己的祖先)。
示例:
3
/ \
5 1
/ \ / \
6 2 0 8
/ \
7 4
- 输入:
p = 5,q = 1
输出:节点3 - 输入:
p = 5,q = 4
输出:节点5(因为 5 是 5 和 4 的公共祖先,且深度最深)
注意:
- 所有节点值唯一。
p和q不同且均存在于二叉树中。
2. 思路分析
2.1 理解“最近公共祖先”
- 从根节点到
p的路径,与根节点到q的路径,它们的最后一个公共节点就是 LCA。 - 如果
p是q的祖先,那么 LCA 就是p。 - 如果
q是p的祖先,那么 LCA 就是q。
2.2 初步思路
一个直观的想法是:
- 分别记录从根到
p和根到q的路径。 - 比较两条路径,找到最后一个相同的节点。
但这样需要存储路径,空间复杂度 O(h)(h 为树高),而且需要两次遍历。
2.3 更优思路:一次后序遍历
我们可以在一次递归遍历中解决:
递归定义:
函数 lowestCommonAncestor(root, p, q) 返回:
- 如果当前子树包含
p但不含q,返回p。 - 如果当前子树包含
q但不含p,返回q。 - 如果当前子树同时包含
p和q,返回它们的最近公共祖先。 - 如果当前子树既不包含
p也不含q,返回null。
关键推理:
- 从叶子往根方向进行后序遍历(左右根)。
- 如果当前节点是
p或q,则返回当前节点。 - 如果左子树返回非空,右子树返回非空,说明当前节点就是 LCA。
- 如果一边非空,一边为空,返回非空那边(表示这边有 p 或 q 或 LCA)。
3. 详细步骤推导
我们以示例树为例,p = 5, q = 4。
树结构:
3
/ \
5 1
/ \ / \
6 2 0 8
/ \
7 4
递归过程(后序:左 → 右 → 根):
-
从根
3开始,先递归左子树(以5为根的子树):- 在
5的左子树:节点6左右都空,返回 null。 - 在
5的右子树:节点2的左子树7返回 null,右子树4是 q,返回4。 - 节点
2的左 null,右返回4,所以2返回4。 - 回到
5:左 null,右返回4,且5自身是 p,所以5返回5(因为 p 和 q 在 5 的子树中,5 就是 LCA)。
- 在
-
根
3的右子树(以1为根):- 遍历后,不包含 p 或 q,返回 null。
-
根
3:左返回5,右返回 null,所以返回5。
最终 LCA 是 5。
4. 算法实现
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
# 递归终止条件
if not root:
return None
if root == p or root == q:
return root
# 后序遍历:先左后右
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestAncestor(root.right, p, q)
# 情况1:左右都不空,当前 root 是 LCA
if left and right:
return root
# 情况2:一边不空,返回不空的那边(可能是 p、q 或 LCA)
if left:
return left
if right:
return right
# 情况3:两边都空,返回 null
return None
5. 复杂度分析
- 时间复杂度:O(N),每个节点最多访问一次。
- 空间复杂度:O(H),递归栈深度,最坏情况 O(N)(树退化为链表)。
6. 关键点总结
- 后序遍历:从底向上查找,天然适合递归回溯。
- 返回值含义:不是直接返回 LCA,而是返回“子树中是否包含 p 或 q 或 LCA”。
- 四种情况:
- 左右均非空 → 当前 root 是 LCA。
- 左非空右空 → 返回左。
- 左空右非空 → 返回右。
- 左右均空 → 返回空。
7. 测试验证
用题目示例测试:
p=5, q=1:在根 3 处,左返回 5,右返回 1,左右都不空 → 返回 3 ✅p=5, q=4:在 5 处,左 null,右返回 4,且自身是 5 → 返回 5 ✅
这样一步步推导,你应该能清晰理解这个题的解法了。需要我进一步解释某个细节吗?