本节目标:
1 . 通过本示例,深刻理解"后序遍历"
2 . 加深"递归"思路模式:明确返回值含义找到切入点
题目介绍
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:"对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。"


cpp
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
}
};
本文约字3300字,阅读+思考约 16 min
检验答案网址:236. 二叉树的最近公共祖先 - 力扣(LeetCode)
若有小伙伴未看过"二叉树"初篇,其实无碍------如果想更加熟悉作者的递归_二叉树逻辑:08 二叉树的中序遍历_二叉树中序遍历题目-CSDN博客
解析
1 . 本题需求也很明确:给你一棵二叉树(TreeNode* root)
2 . 要求返回 p 和 q 两个结点的最近公共祖先(这个祖先也是TreeNode*)
3 . 二叉树的题就是这样,树型结构决定了------一棵树可以用当前树的根节点表示,反过来:一个结点(地址)其实可以代表以该结点为根的树
4 . 二叉树------递归不分家

解释:
a. 首先确定p, q位置后
b. 从目标结点(p或是q)向上返回
c. 可以发现,p 和 q的结点恰好是p
递归_二叉树
1 . 这是现成的二叉树------递归思路
2 . 怎么递归呢?
确定好两样:核心任务 和 递归出口
a . 核心任务:对当前树的根节点进行的操作
b . 递归出口:对传入参数的合法性进行判断
3 . 首先是核心任务:
技巧------若直接无法推断核心任务,可结合传入的参数和返回值进行辅助推导
4 . 比如此处:
a . 言之要有物,这就把示例树拿出来
b . 对于当前树(比如结点3)就代表了整棵树,它拥有的参数:root , p , q
c . 无疑我能靠root 得到root->left root->right
d . 也能找到p , q 这两棵树(如果存在,一定能找到
5 . 继续上例的核心任务拆解:
a . 一棵树操作完毕后,返回值类型为TreeNode*
b . 若结合上图,最好返回的结点应该是找到的p,q的祖先/公共祖先
c . 似乎还不够清楚:但是我们能得到结论,我们很重视当前树的root->left 和 root->right的返回值
6 . 可以说,如果遇到root == p || root->q 我们就可以不再往下;毕竟找的是p 和 q的祖先而不是孩子
7 . 那么此时返回值无疑是root(也只有root这个变量是TreeNode*,能够返回)
步骤6 、7
8 . 现在来到结点2为根节点的树:现在它得到自己右孩子的返回值;那左孩子的返回值呢?图上看似没画,其实也很明显了
9 . 结点2的左孩子树的返回值应该是无用值,找来找去应当用nullptr
步骤8 、9
10 . 那么我问你?对于2结点,应该向上返回谁的地址呢?------是结点2的地址还是,结点2得到的结点4的地址
11 . 不纠结,先写一点代码:
cpp
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == p || root == q)
return root;//当找到p 或者 q 直接可以向上返回
TreeNode* left_result = lowestCommonAncestor(root->left,q,p);
TreeNode* right_result = lowestCommonAncestor(root->right,p,q);
// ...
return nullptr// ?虽不知具体返回谁,但目标是返回p,q的公共祖先
}
12 . 我们拿一个普遍的例子:(p 和 q的公共祖先是第三个结点)
13 . 根据代码:现在结点3(这棵树)得到了 来自左子树得到的TreeNode* 和 右孩子得到的TreeNode*
14 . 此时这棵树(以结点3为根)应该返回谁呢?从图上看出来了,应该返回root(也就是本结点)
15 . 对于这个现象,我们也很好理解:当左子树得到了p 的地址,右子树得到了q的地址(因为都是有效地址),所以最后返回的就是二者的root(p、q的公共祖先)
16 . 总结一下核心任务:当前节点在做决策前,已经拿到了左右子树的 "完整反馈"(left_result/right_result),知道左右子树是否找到 p/q------向上级返回情况a . 若left_result//right_result 单边找到,很明显无法返回root。只能老实返回left_result//right_result,让上级找到另一边
b . 若left_result//right_result两边都找到,才能向上返回root(表示root就是这个公共结点)
cpp
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == p || root == q)
return root;
TreeNode* left_result = lowestCommonAncestor(root->left,q,p);
TreeNode* right_result = lowestCommonAncestor(root->right,p,q);
// ...
if(left_result && right_result)
return root;
else if(left_result)
return left_result;
else return right_result;
}
};
17 . 递归出口:root 不能为空
cpp
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == nullptr)
return nullptr;
if(root == p || root == q)
return root;
TreeNode* left_result = lowestCommonAncestor(root->left,q,p);
TreeNode* right_result = lowestCommonAncestor(root->right,p,q);
if(left_result && right_result)
return root;
else if(left_result)
return left_result;
else return right_result;
}
};
18 . 为何我说本节能加深理解"后序遍历"?
答:因为本题在考虑核心任务时,这切入点正是后序遍历得到的结果:
注:左 -> 右 -> 根
cpp
TreeNode* left_result = lowestCommonAncestor(root->left,q,p);
TreeNode* right_result = lowestCommonAncestor(root->right,p,q);
19 . 破局的关键------如何抓住真正的切入点:即明确返回值的含义
注:这里的返回值应该是:p 和 q 的公共祖先(也很有可能是p 或者 q)
20 . 知晓返回值含义后,我们若要靠近目标------找到q,p的公共祖先,其实这返回值正好可以为我们提供信息。那么先后关系就有了:应该先得到左右子树关于p/q的反阔,再在本树下做决策
二叉树开篇之作:08 二叉树的中序遍历_二叉树中序遍历题目-CSDN博客 类似后序遍历之作:09 二叉树的最大深度-CSDN博客
总结以及完整参考代码

cpp
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == nullptr)
return nullptr;
if(root == p || root == q)
return root;
TreeNode* left_result = lowestCommonAncestor(root->left,q,p);
TreeNode* right_result = lowestCommonAncestor(root->right,p,q);
if(left_result && right_result)
return root;
else if(left_result)
return left_result;
else return right_result;
}
};
愿你的快乐没有终止条件,幸福的递归深度无限延伸,所有的烦恼都在回溯中释放,2026 马年,返回值全是好运!

步骤6 、7
步骤8 、9