目录
一、题目
236. 二叉树的最近公共祖先 - 力扣(LeetCode)
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 输出:3 解释:节点5和节点1的最近公共祖先是节点3
示例 2:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 输出:5 解释:节点 5和节点4的最近公共祖先是节点5,因为最近公共祖先节点可以为节点本身
示例 3:
输入:root = [1,2], p = 1, q = 2 输出:1
提示:
- 树中节点数目在范围
[2, 105]
内。 -109 <= Node.val <= 109
- 所有
Node.val
互不相同
。 p != q
p
和q
均存在于给定的二叉树中。
二、必备知识
首先,我们要明确的一点是,树的题目绝大部分都可以从递归的角度思考,因为树本身的结构就是一个递归结构,故我们先从递归的角度分析,看能否顺利解决。
那么,一个清晰且正确的树的递归包含哪些方面呢?
1、终止条件
2、返回值
3、递推工作
三、思路分析
在上面我们已经知道了一个合格的递归包含哪些方面,接下来我们将它实际应用于本题之中
1、最近公共祖先破题
首先,我们要理解最近公共祖先的定义
定义:对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。
①祖先
最近公共祖先<=>左右子树和自身节点加在一起可以将p、q全部包含进去
故可以分为以下2种情况:
1、自身节点是p,左或右子树是q(自身节点是q,左或右子树是p)
2、自身节点非p或q,但p和q分别位于它的左右子树之中
(注意一定是分别位于左、右,这样才能保证p和q一定可以连在一起!)
即
②最近
大家可能会疑惑这样找出来的公共祖先是最近的吗,即是深度最大的吗?
答案是是,因为我们在递归中是自底向上从叶子节点开始更新的,所以在所有满足条件的公共祖先中一定是深度最大的公共祖先先被访问到。
2、代码思路
根据上面的分析和必备知识中递归的架构,我们来将上面的框架填充完整
1、终止条件
①越过叶子节点时,直接返回NULL
②当root等于p,q时,直接返回root
(此处包含两层含义:1、公共祖先可以是自己2、说明当前节点含有p或q)
2、递推工作
①开始递归左子树,返回值记为left
②开始递归右子树,返回值为right
3、返回值
①left和right同时为空:即root的左右子树都不包含p、q,返回null
②left和right同时不为空:即p、q分别在左右子树,此时root为最近公共祖先,返回root
③left为空,right不为空:即p、q都不在左子树中,直接返回right,具体可分为2种情况:
a.p、q其中一个在root的右子树中,此时right指向p或q
b.p、q两个节点都在root的右子树中,此时right指向最近公共祖先节点
(因为root的在两个地方均有返回:1、当root等于p,q时,直接返回root。2、left和right同时不为空:即p、q分别在左右子树,此时root为最近公共祖先,返回root。)
④right为空,left不为空:即p、q都不在右子树中,直接返回left,具体情况和上同理
四、代码
具体的思考过程在代码注释中均有,十分详细
//递归中最重要的:定义子问题
//最近公共祖先<=>左右子树分别包含p、q
//递归树主要确定3个方面:1、递归出口2、递推工作3、返回值
class Solution {
public:
//自底向上更新叶子节点,所以得到的一定是最深公共祖先
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//递归出口
//==p||q直接返回root
//包含两层含义
//1、公共祖先可以是自己(最“低”祖先)
//2、含有p或q
if(root==nullptr||root==p||root==q) return root;
//开始递归,寻找子树和p、q的关系
//为什么这里是先递归呢?
//因为这里是通过子树才能判断当前节点
//所以是先递归再给返回值
//分别找左子树和右子树
TreeNode* left=lowestCommonAncestor(root->left,p,q);
TreeNode* right=lowestCommonAncestor(root->right,p,q);
//因为没有重复结点,所以可以直接这样判断
//同时为空,当前节点不可能是公共祖先
if(left==nullptr&&right==nullptr) return nullptr;
if(left==nullptr) return right;
if(right==nullptr) return left;
return root;//一定要有非if的返回条件
}
};
有一点非常基础的地方需要注意的是:
根据上面搭建的代码框架,最后四步其实是4个分支,即if(left!=nullptr&&right!=nullptr) return root; if(left==nullptr&&right==nullptr) return nullptr; if(left==nullptr) return right; if(right==nullptr) return left;
但是有返回值的函数一定要有一个非if的返回值(即通用的),否则会编译出错
所以我们去掉一个分支,将它直接写成:
if(left==nullptr&&right==nullptr) return nullptr; if(left==nullptr) return right; if(right==nullptr) return left; return root;
上面的代码还可以简化为以下版本
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;
}
};