【程序员面试金典】面试题 04.08. 首个共同祖先

文章介绍了如何设计算法在二叉树中找到两个指定节点的最近公共祖先,提供了两种解题思路:一是利用路径数组记录遍历过程,二是通过递归遍历左右子树。第一种方法需要额外存储节点路径,第二种方法则避免了额外的数据结构,直接通过递归实现。
摘要由CSDN通过智能技术生成

【程序员面试金典】面试题 04.08. 首个共同祖先

题目描述

描述:设计并实现一个算法,找出二叉树中某两个节点的第一个共同祖先。不得将其他的节点存储在另外的数据结构中。注意:这不一定是二叉搜索树。

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

3
   / \
  5   1
 / \ / \
6  2 0  8
  / \
 7   4
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。

解题思路

思路1:最直观的想法是,遍历二叉树,分别记录从根节点到p的路径数组path1和从根节点到q的路径数组path2,然后遍历path1和path2,找到其最后一个相同的节点,其即为p和q的最近公共祖先。注意,遍历从根节点到某一节点的路径使用的是前序遍历,因为路径从根节点开始。其中需要使用一个标记变量flag来标记当前路径是否完成,当当前节点为目标节点时其不需要再继续遍历,故我们需要设置边界条件即当flag为true则直接返回。由于先序遍历是根左右,故先遍历左边,那如果我们的目标在右边,前面有数据压入但其不符合要求,故我们需要使用回溯,即当前节点压入数组后,如果其左右子树遍历均结束,且flag仍未被标记为true,则表明当前节点不在路径中,故应该将当前节点弹出。

vector<TreeNode*> path1;
vector<TreeNode*> path2;
bool flag=false;
void dfs(TreeNode* root,TreeNode* target,vector<TreeNode*> &path)
{
    if(root==NULL||flag==true) return;
    path.push_back(root);
    if(root==target) 
    {
        flag=true;  //标记访问结束
        return;
    }
    dfs(root->left,target,path);
    dfs(root->right,target,path);
    if(!flag) path.pop_back(); //没找到才弹出
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) 
{
    dfs(root,p,path1);
    flag=false;
    dfs(root,q,path2);
    TreeNode* res=NULL;
    for(int i=0,j=0;i<path1.size()&&j<path2.size();i++,j++)
    {
       if(path1[i]==path2[j])
          res=path1[i];
    }
    return res;
}

思路2:上述方法算是暴力求解,但是题目中要求不得将其他节点存储在另外的数据结构中,故我们需要考虑有无其他方法来实现。我们可以在整棵树上进行遍历并判断!lowestCommonAncestor表示在以root为根节点的二叉树上寻找p和q的最近公共祖先,那么left表示在以root的左子树为根节点的二叉树上寻找p和q的最近公共祖先,right表示在以root的右子树为根节点的二叉树上寻找p和q的最近公共祖先,如果找到p返回p,如果找到q返回q,如果找到空返回空,这时我们就判断left和right,如果left为空则表示左子树没有即返回right,如果right为空则表示右子树没有即返回left,反之left和right均为空则表示左右子树均没有即返回root啦!注意递归逻辑思考过程!

TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) 
{
    if(root==NULL) return NULL;
    // 找到p则返回p
    if(root==p) return p;
    // 找到q则返回q
    if(root==q) return q;
    //寻找以root左子树为根节点的p和q的公共祖先
    TreeNode* left=lowestCommonAncestor(root->left,p,q);
    //寻找以root右子树为根节点的p和q的公共祖先
    TreeNode* right=lowestCommonAncestor(root->right,p,q);
    //左子树未找到返回右子树
    if(!left)
       return right;
    //右子树未找到返回左子树
    if(!right)
       return left;
    return root; //都未找到返回root
}

总结:在想递归的时候,可以先想当前函数是干什么的,然后怎么将其分解为在左右子树上,然后先想第一层,再举个例子就明白了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值