### 方法思路
该算法使用递归方法在二叉树中查找两个节点 `p` 和 `q` 的最近公共祖先(LCA)。其核心思想是通过后序遍历自底向上搜索,一旦在当前节点的左子树和右子树中分别找到 `p` 或 `q`,则该当前节点即为它们的LCA。### 解决代码
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
// 递归终止条件:当前节点为空或找到p/q时返回当前节点
if (root == nullptr || root == p || root == q) return root;
// 递归遍历左子树,查找p或q
TreeNode* left = lowestCommonAncestor(root->left, p, q);
// 递归遍历右子树,查找p或q
TreeNode* right = lowestCommonAncestor(root->right, p, q);
// 如果左右子树都未找到p/q,返回空
if (left == nullptr && right == nullptr) return nullptr;
// 左子树未找到,返回右子树的结果(可能包含p/q或LCA)
if (left == nullptr) return right;
// 右子树未找到,返回左子树的结果(可能包含p/q或LCA)
if (right == nullptr) return left;
// 左右子树均找到,说明当前节点是LCA
return root;
}
};
好的,我们可以通过详细的递归步骤来更清晰地理解该代码是如何一步步找出最近公共祖先的。我们以一个例子进行详细的运行分析。
二叉树结构(假设):
3
/ \
5 1
/ \ / \
6 2 0 8
/ \
7 4
假设我们需要找出节点 p = 5 和节点 q = 1 的最近公共祖先。现在详细分析每一步递归的过程。
初始化:
我们传入 root = 3,p = 5,q = 1,目标是找到这两个节点的最近公共祖先。
第一步:调用 lowestCommonAncestor(root=3, p=5, q=1)
• 检查当前节点: root == 3,它既不是空指针,也不是 p 或 q,所以继续递归。
• 递归左子树: 调用 lowestCommonAncestor(root=5, p=5, q=1)。
• 递归右子树: 调用 lowestCommonAncestor(root=1, p=5, q=1)。
第二步:调用 lowestCommonAncestor(root=5, p=5, q=1)
• 检查当前节点: root == 5,这个节点与 p 相等,因此直接返回当前节点 5。
• 返回:left = 5。
第三步:调用 lowestCommonAncestor(root=1, p=5, q=1)
• 检查当前节点: root == 1,这个节点与 q 相等,因此直接返回当前节点 1。
• 返回:right = 1。
第四步:回到 root=3
• 左子树的返回值: left = 5(表示在左子树中找到了 p)。
• 右子树的返回值: right = 1(表示在右子树中找到了 q)。
由于左子树和右子树的返回值都不为空,这意味着节点 5 和节点 1 分别位于左右子树,因此当前节点 3 是它们的最近公共祖先。
• 返回: root = 3。
总结
通过递归遍历,最终找到了 p = 5 和 q = 1 的最近公共祖先是节点 3。下面是每一步的详细执行过程:
1. 首先判断当前根节点 3,它既不等于 p 也不等于 q,所以继续向左右子树递归。
2. 左子树递归到了节点 5,返回 5。
3. 右子树递归到了节点 1,返回 1。
4. 最后在根节点 3,发现左右子树分别有 p 和 q,因此返回根节点 3 作为最近公共祖先。
递归过程的可视化
• lowestCommonAncestor(3, 5, 1)
→ 递归左子树 lowestCommonAncestor(5, 5, 1) → 返回 5
→ 递归右子树 lowestCommonAncestor(1, 5, 1) → 返回 1
→ 左右子树均不为空,返回当前节点 3
其他情况的处理
如果我们传入不同的节点,例如:
1. 如果 p 和 q 都在同一子树中:
假设我们需要找 p = 6 和 q = 7,它们都在左子树,代码会在递归时最终返回 6 或 7 作为公共祖先。
2. 如果 p 或 q 不存在于树中:
如果 p 或 q 在树中不存在,代码会最终返回 nullptr。例如,如果我们传入不存在的节点 p = 10 和 q = 11,递归到树的叶子节点时会返回 nullptr。
3. 递归终止条件:
每次递归都会检查当前节点是否为空或是否是 p 或 q,如果是,就直接返回当前节点。如果都没有找到,就会继续在左右子树中寻找。
通过这样的递归过程,函数最终会返回正确的最近公共祖先。每次递归的步骤都清楚地限定了查找范围:要么在左子树,要么在右子树,最终通过左右子树的返回结果来判断当前节点是否是 LCA。