在二叉树中找到两个节点的最近公共祖先

在二叉树中找到两个节点的最近公共祖先

描述
给定一棵二叉树(保证非空)以及这棵树上的两个节点对应的val值 o1 和 o2,请找到 o1 和 o2 的最近公共祖先节点。

数据范围:1≤n≤1000,树上每个节点的val满足0<val≤100
要求:时间复杂度 O(n)

注:本题保证二叉树中每个节点的val值均不相同。

如当输入[3,5,1,6,2,0,8,#,#,7,4],5,1时,二叉树{3,5,1,6,2,0,8,#,#,7,4}如下图所示:

所以节点值为5和节点值为1的节点的最近公共祖先节点的节点值为3,所以对应的输出为3。
节点本身可以视为自己的祖先
示例1
输入:
[3,5,1,6,2,0,8,#,#,7,4],5,1

返回值:
3

方法一:递归

思路: 不妨假设一个递归描述函数LCA(TreeNode* root,int o1,int o2)的返回值是要找的公共祖先,那么问题就可以分为三种情况:

  • 1.如果值为o1o2的节点在左子树上,那么返回LCA(TreeNode* root->left,int o1,int o2);

  • 2.如果值为o1o2的节点在右子树上,那么返回LCA(TreeNode* root->right,int o2,int o2);

  • 3.如果o1o2分别在左子树和右子树上,那么当前父节点就是答案!

struct TreeNode {
 	int val;
 	struct TreeNode *left;
 	struct TreeNode *right;
};
class Solution {
public:
    
    TreeNode* LCA(TreeNode* root,int o1,int o2){//需要重新写一个函数找到并返回祖先节点
    	//当父节点值为o1或o2时,此时root即为答案
        if(root->val==o1 || root->val==o2) return root;
        //递归去找左右子树,若只在其中一棵子树上,那么递归找到的结果为答案;
        //否则在“分开”在两棵子树上的话,当前“分开”的节点即为他们的公共祖先
        TreeNode* l=LCA(root->left, o1, o2);
        TreeNode* r=LCA(root->right, o1, o2);
        if(l==nullptr) return r;//判断是否“分开”或答案在右子树上
        if(r==nullptr) return l;//判断是否“分开”或答案在左子树上
        return root;//“分开”则返回当前父节点
    }
    int lowestCommonAncestor(TreeNode* root, int o1, int o2) {
        TreeNode* ret=LCA(root, o1, o2);
        return ret->val;
    }
};
  • 时间复杂度: 最差遍历N个节点,因此为O(N);
  • 空间复杂度: 最差情况下,所有节点都会遍历,树退化成链表,递归消耗的栈空间为O(N),最好情况为O(1),因此平均复杂度为O(logN)

方法二:非递归(图)

思路: 整体的思路就是找到两个值对应的节点到根节点路径的交点就是答案,因此这里就得用到图

  1. 首先利用队列对树进行层次遍历,将遍历时的父子关系保存在一张无序图f中,直到找到值为o1o2的两个节点则终止遍历即可;
  2. 然后在图中从其中一个节点(o1o2)出发,在树中从下到上找到最上的根节点,记录下这条路径到无序集合path中;
  3. 最后在从另一个节点(o2o1)出发,在树种从下到上,直到找到与之前路径中一样的值的节点,这个节点就是两条路径的交点,也就是他们的公共祖先;
  4. 如果遍历到最上的根节点还没找到答案,那么根节点就是答案。
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
};

class Solution {
public:
    
    int lowestCommonAncestor(TreeNode* root, int o1, int o2) {
        unordered_map<int, int> f;//只记录路径,无序图即可
        queue<TreeNode*> que;//队列对树进行层次遍历
        que.push(root);
        //首先层次找到两个值对应的节点及路径上的父子关系,保存在图中
        while(!f[o1] || !f[o2]){//遍历终点是找到两个值,即对应的图值不为空
            TreeNode* node=que.front(); que.pop();
            
            TreeNode* l=node->left;
            TreeNode* r=node->right;
            
            if(l!=nullptr){
                f[l->val]=node->val;//保存父子关系
                que.push(l);
            }
            if(r!=nullptr){
                f[r->val]=node->val;
                que.push(r);
            }
        }
        unordered_set<int> path;//将其中一条路径保存到集合中,以便找交点
        while(f[o1]){
            path.insert(o1);
            o1=f[o1];
        }
        while(path.find(o2)==path.end() && o2!=root->val){//遍历另一条路径,找到交点则结束遍历
            o2=f[o2];
        }
        return o2;
    }
};
  • 时间复杂度: 遍历所有节点N,故为O(N);
  • 空间复杂度: 申请的容器需要装N个节点,故也为O(N)
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值