二叉树的最近公共祖先(递归经典题目)
最近刷新到一道很有趣的题目,可以说是比较经典的递归题目,如果对于递归了解不够透彻,就不能或者不能很好的做出这道题目。
下面先看下题目描述:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,
满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
如图:
示例:
示例 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。因为根据定义最近公共祖先节点可以为节点本身。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这种题一看就是二叉树进行深度优先遍历,然后在对数值进行处理,所以这里先回忆一下,
深度优先遍历的基本结构:
深度优先遍历二叉树
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
void dfs(TreeNode *tn)
{
if(!tn){ //边界条件,为空返回
return;
}
// 这里写需要处理的数据代码
//...
dfs(tn->left); //先递归遍历左节点
dfs(tn->right); //在递归遍历右节点
}
注:新手刚开始看的时候,可能比较难理解递归的意思,这个时候可以先跟着代码敲,反复琢磨。当你见多了,用多了,也就明白是怎么一回事了。
下面将会展示两种解法:
(1)首先是官方的标准答案:
官方的思路是标准的深度优先遍历方法:
1.直接调用递归,如果节点root为空返回空,如果节点root为需求节点,返回该节点
2.然后再判断两个递归的返回值,确定要返回什么节点。
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (!root || root == p || root == q) //如果到边界(为空),或者遇到需求值,直接返回
return root;
auto left = lowestCommonAncestor(root->left, p, q); //获取左节点的值
auto right = lowestCommonAncestor(root->right, p, q); //获取右节点的值
// 这个判断语句的意思是,如果两个都为空,随便返回一个空值,
// 如果一个为空一个不空,返回不空
//如果两个都不空,返回root(即root就是两个节点p,q的最近公共祖先)
if (!left) //左值为空,返回有值
return right;
if (!right) //有值为空,返回左值
return left;
return root; //两个都为空的时候,返回递归到节点 root
}
分析:这个代码是比较经典的阐释了递归算法,其需要遍历所有节点,所以时间复杂度是O(n),空间复杂度是O(1)
(2)我的思路
1.深度优先遍历二叉树,用vector数组保存当前走的路径;
2.如果遇到需求节点,则记录下来
3.当遍历遇到两个需求节点时,结束递归
分析:因为当结果满足时就不再遍历,再加上还有遍历保存下来的两条路径,这个最坏的时间复杂度 是O(2N),平均时间复杂度应该是O(2logN);因为需要记录路径,所以空间复杂度O(3logN)<=x<O(3N)
注:代码比较长,空间复杂度也没有方法一少,但是如果是大数据处理,内存吃得消时,耗时应该优于方法一
下面直接上代码:
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
//深度优先获取路径
vector< TreeNode *> vq; //存取根节点到 TreeNode* q的路径
vector< TreeNode *> vp; //存取根节点到 TreeNode* p的路径
void dfsGetPath(TreeNode *Tn, int index, vector< TreeNode *> &vec, TreeNode* q, TreeNode* p)
{
if (!Tn) {
return;
}
if (vp.size() != 0 && vq.size() != 0) // 已经取到节点,不再继续递归
return;
if (index + 1 < vec.size()) { //再次插入index + 1的位置
vec[index + 1] = Tn; //插入数据
}
else {
vec.emplace_back(Tn); //首次插入 index + 1的位置
}
if (Tn == q || Tn == p) {
if (Tn == p) {
vp.assign(vec.begin(), vec.begin() + index + 2); //将路径赋值给 vp
}
else {
vq.assign(vec.begin(), vec.begin() + index + 2); //将路径赋值给 vq
}
}
dfsGetPath(Tn->left, index + 1, vec, q, p); //递归左节点
dfsGetPath(Tn->right, index + 1, vec, q, p); //递归右节点
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
vector< TreeNode *> vec;
dfsGetPath(root, -1, vec, p, q);
int i = 0;
while (true) { // 遍历两个路径
if (vp[i] == vq[i]) {// 公共祖先节点
// 如果某条路径已走完,则i就是最深的的公共祖先节点
if(!(i + 1 < vp.size() && i + 1 < vq.size()))
return vp[i];
//如果下一个路径节点不相同,则I就是最深的公共祖先节点
else if (vp[i + 1] != vq[i + 1]) {
return vp[i];
}
}
i++;
}
return root;
}
最后:如果不够严谨之处,请指出,大家一起交流。