LeetCode236. 二叉树的最近公共祖先(递归经典题目)

二叉树的最近公共祖先(递归经典题目)

最近刷新到一道很有趣的题目,可以说是比较经典的递归题目,如果对于递归了解不够透彻,就不能或者不能很好的做出这道题目。

下面先看下题目描述:

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 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;
}

最后:如果不够严谨之处,请指出,大家一起交流。

猜你喜欢

转载自blog.csdn.net/h799710/article/details/106089298
今日推荐