LeetCode236. The nearest common ancestor of the binary tree (recursive classic topic)

The nearest common ancestor of a binary tree (recursive classic topic)

Recently refreshed to a very interesting topic, it can be said that it is a more classic recursive topic. If you do not have a thorough understanding of recursion, you cannot or cannot make this topic well.

Let's look at the title description first:

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

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,
满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4]
如图:

Binary tree
Example:

示例 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
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

At first glance, this kind of problem is that the binary tree performs depth-first traversal and then processes the values, so here is
the basic structure of depth-first traversal:

Depth-first traversal of binary trees
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);		//在递归遍历右节点
}

Note: When a novice is first looking at it, it may be difficult to understand the meaning of recursion. At this time, you can follow the code and ponder over and over again. When you see more and use more, you will understand what is going on.

Two solutions will be shown below:

(1) First, the official standard answer:

The official idea is the standard depth-first traversal method:
1. Directly call recursion, if the node root is empty, return empty, if the node root is a demand node, return to the node
2. Then judge the two recursive return values, and be sure to return What node.

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
}

Analysis: This code is a more classic explanation of the recursive algorithm, which needs to traverse all nodes, so the time complexity is O(n), and the space complexity is O(1)

(2) My thinking

1. Depth-first traversal of the binary tree, save the current path with a vector array;
2. If a demand node is encountered, record it
3. When the traversal encounters two demand nodes, end the recursive
analysis: because it will not be when the result is satisfied Then traverse, plus traverse the two saved paths. The worst time complexity is O(2N), and the average time complexity should be O(2logN); because the path needs to be recorded, the space complexity is O (3logN)<=x<O(3N)

Note: The code is relatively long, and there is no way to reduce the space complexity, but if it is big data processing, the memory is time-consuming, and the time-consuming should be better than that of the method 1.
Below, directly upload the code:

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;
}

Finally: If you are not rigorous enough, please point it out and communicate with everyone.

Guess you like

Origin blog.csdn.net/h799710/article/details/106089298