GitHub_Trending/le/LeetCode-Book:二叉树最近公共祖先问题详解
引言:你还在为LCA问题抓狂吗?
在二叉树(Binary Tree)相关算法题中,最近公共祖先(Lowest Common Ancestor, LCA) 问题被誉为"面试高频考点"。无论是求职面试还是算法竞赛,这个问题都频繁出现,且衍生出多种变形。本文将基于LeetCode-Book项目中的经典题解,从定义解构、算法推导到代码实现,全方位剖析两种最典型的LCA场景——二叉搜索树(BST) 和普通二叉树的LCA求解方案。读完本文,你将掌握:
- 3种LCA存在形式的数学定义
- BST的2种最优解法(迭代/递归)及复杂度对比
- 普通二叉树的回溯算法实现原理
- 3种编程语言(Python/Java/C++)的代码模板
- 5道扩展习题及解题思路
一、概念解构:什么是最近公共祖先?
1.1 祖先与最近公共祖先的数学定义
祖先(Ancestor):若节点p在节点root的左/右子树中,或p = root,则称root是p的祖先。
最近公共祖先:设root为p和q的公共祖先,若其左子节点root.left和右子节点root.right都不是p和q的公共祖先,则root是"最近的公共祖先"。
1.2 LCA的三种存在形式
二、二叉搜索树(BST)的LCA问题
2.1 问题特性与解题关键
BST的左<根<右特性为LCA问题提供了捷径:
- 若
root.val < p.val且root.val < q.val→ p,q都在右子树 - 若
root.val > p.val且root.val > q.val→ p,q都在左子树 - 否则,
root即为LCA(包含p/q为root的情况)
2.2 方法一:迭代法(最优解)
算法流程
代码实现
Python版本
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
# 优化:确保p.val <= q.val,减少判断条件
if p.val > q.val:
p, q = q, p
while root:
if root.val < p.val: # 都在右子树
root = root.right
elif root.val > q.val: # 都在左子树
root = root.left
else: # 找到LCA
break
return root
Java版本
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (p.val > q.val) {
TreeNode temp = p;
p = q;
q = temp;
}
while (root != null) {
if (root.val < p.val)
root = root.right;
else if (root.val > q.val)
root = root.left;
else
break;
}
return root;
}
}
C++版本
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (p->val > q->val)
swap(p, q);
while (root != nullptr) {
if (root->val < p->val)
root = root->right;
else if (root->val > q->val)
root = root->left;
else
break;
}
return root;
}
};
复杂度分析
- 时间复杂度:O(N),最坏情况为链表结构(退化为单支树)
- 空间复杂度:O(1),仅使用常数级额外空间
2.3 方法二:递归法
算法流程
代码实现(Python)
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if root.val < p.val and root.val < q.val:
return self.lowestCommonAncestor(root.right, p, q)
if root.val > p.val and root.val > q.val:
return self.lowestCommonAncestor(root.left, p, q)
return root
复杂度分析
- 时间复杂度:O(N),同迭代法
- 空间复杂度:O(N),递归栈深度最坏为N
2.4 BST的LCA解法对比表
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 | 核心优势 |
|---|---|---|---|---|
| 迭代 | O(N) | O(1) | 内存敏感场景 | 无栈溢出风险 |
| 递归 | O(N) | O(N) | 代码简洁性优先 | 实现简单,可读性高 |
三、普通二叉树的LCA问题
3.1 问题难点与解决思路
普通二叉树没有BST的有序特性,需通过后序遍历(DFS) 实现回溯:
- 从底至顶搜索p和q
- 若同时在左右子树找到p和q,则当前节点为LCA
- 若只在一侧找到,则将找到的节点向上传递
3.2 递归回溯算法
算法流程
代码实现
Python版本
class Solution:
def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
if not root or root == p or root == q:
return root
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if not left: # 左子树未找到,返回右子树结果
return right
if not right: # 右子树未找到,返回左子树结果
return left
return root # 左右都找到,当前节点为LCA
Java版本
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) return right;
if (right == null) return left;
return root;
}
}
C++版本
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == nullptr || root == p || root == q) return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if (left == nullptr) return right;
if (right == nullptr) return left;
return root;
}
};
复杂度分析
- 时间复杂度:O(N),需遍历所有节点
- 空间复杂度:O(N),递归栈深度最坏为N(链表结构)
3.3 算法正确性证明
采用反证法证明算法有效性:
- 假设存在更近的公共祖先
X - 则
X必在root的左或右子树中 - 此时算法会在
X处检测到左右子树同时存在p和q - 与假设矛盾,故root为最近公共祖先
四、实战应用与扩展
4.1 核心算法模板总结
BST的LCA迭代模板
def bst_lca_iterative(root, p, q):
if p.val > q.val:
p, q = q, p
while root:
if root.val < p.val:
root = root.right
elif root.val > q.val:
root = root.left
else:
return root
return None
普通二叉树LCA递归模板
def tree_lca_recursive(root, p, q):
if not root or root == p or root == q:
return root
left = tree_lca_recursive(root.left, p, q)
right = tree_lca_recursive(root.right, p, q)
return root if left and right else left or right
4.2 扩展习题与解题提示
-
二叉树的LCA II(含父指针)
- 提示:转化为求两个链表的第一个公共节点
-
N叉树的LCA
- 提示:将左右子树判断扩展为遍历所有子节点
-
LCA的批量查询优化
- 提示:预处理树为欧拉序列+ST表,实现O(1)查询
-
带权树的LCA(求路径和)
- 提示:记录节点深度和到根节点距离,
distance(p,q) = dist(p) + dist(q) - 2*dist(lca)
- 提示:记录节点深度和到根节点距离,
-
二叉搜索树的LCA III(含重复值)
- 提示:修改判断条件为
(root.val - p.val) * (root.val - q.val) <= 0
- 提示:修改判断条件为
4.3 项目实战:LeetCode-Book中的LCA问题
LeetCode-Book项目中关于LCA的完整实现位于:
- 二叉搜索树:
sword_for_offer/docs/剑指 Offer 68 - I.md - 普通二叉树:
sword_for_offer/docs/剑指 Offer 68 - II.md
完整代码可通过以下命令获取:
git clone https://gitcode.com/GitHub_Trending/le/LeetCode-Book
五、总结与升华
5.1 知识体系梳理
5.2 工程化思考
在实际开发中选择LCA算法时需考虑:
- 树的类型:BST优先选迭代法(O(1)空间)
- 树的规模:大型树需考虑栈溢出风险(递归深度限制)
- 查询频率:高频查询场景需预处理(如ST表、二进制 lifting)
5.3 学习建议
掌握LCA问题的三个阶段:
- 基础阶段:熟练实现BST和普通二叉树的标准解法
- 优化阶段:理解各种优化算法的适用场景
- 扩展阶段:将LCA思想应用于图论、路径规划等领域
本文代码均来自LeetCode-Book开源项目,遵循Apache 2.0开源协议。如需深入学习,建议结合项目中提供的单元测试和调试用例进行实践。收藏本文,下次遇到LCA问题,你就是面试中的解题高手!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



