leetcode236.C++解决二叉树的最近公共祖先

二叉树的最近公共祖先

先看题目描述:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这道题目我给出两种写法:

思路一

运用后序遍历的回溯过程,如果找到了我想要的节点就像上层返回该节点,没有找到就返回空。首先可以观察到如果一个节点在root的左子树,另一个在右子树,root就是公共祖先;如果两个都在root的左(右)子树,则公共祖先是其中一颗子树返回的节点。
解题步骤见下图:
在这里插入图片描述
针对于上图中第三种特殊情况,我们不用关心p节点是否能找到,因为递归到了q就会向上返回,并且q就是我们要的最近公共祖先。
下面给出了C++实例代码:

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == nullptr) return nullptr; // 遇到了空节点就说明没找到, 返回空
        if(root == p || root == q) return root; // 遇到p或q向上返回对应的节点

        TreeNode* left = lowestCommonAncestor(root->left, p, q); // 左子树给根节点的返回值
        TreeNode* right = lowestCommonAncestor(root->right, p, q);// 右子树给根节点的返回值
        if(left == nullptr && right != nullptr)
            return right;
        else if(left != nullptr && right == nullptr)
            return left;
        else if(left != nullptr && right != nullptr)
            return root;
        else
            return nullptr;
    }
};

复杂度分析
时间复杂度 O(N) : 其中 N 为二叉树节点数;最坏情况下,二叉树近似于链式结构,需要递归遍历树的几乎所有节点。
空间复杂度 O(N) : 最差情况下,递归深度达到 N ,需要使用 O(N) 大小的额外空间。
在这里插入图片描述

思路二

DFS求出从根节点到p和q的路径放在容器中,转换成路径相交问题。
具体解题步骤如下图:首先遍历二叉树,将一路上遇到的不等于p或q的节点统统入栈,直到遇到p或q就向上层返回。可能有些人会有疑问,那不在p或q路径上的节点不是我们想要的,如何出栈呢?
在这里插入图片描述
对于上面图的5节点的左子树-6节点而言,它的左右子树均未找到p和q,于是将它pop掉;然后到了5的右子树,5的右子树先 DFS 2的左子树-7节点,刚好7节点就是q,注意不用去 DFS 2的右子树了,直接向上返回true,此时栈上保存的数据就是从根节点到q的路径。结合代码去理解:

bool Getpath(TreeNode* root, TreeNode* n, stack<TreeNode*>& path)
{
    if(root == nullptr) return false;
    // 将路过的节点都入栈
    path.push(root);
    if(root == n) return true;
    // 没找到就继续往下找
    if(Getpath(root->left, n, path)) return true;
    if(Getpath(root->right, n, path)) return true;
    // 左右子树都遍历完了都没找到
    path.pop();
    return false;
}

将p和q的路径保存到栈之后,下面就是要想办法找到最近公共节点。
这种思想和leetcode160.相交链表的思想是一样的。
第一步就是让快指针从长度较长的链表头结点先走gap步,gap为两链表长度的差值的绝对值;在这道题里,我们将栈里面元素最多的那个栈一直pop,直到两栈元素相等。

stack<TreeNode*> ppath, qpath;
while(ppath.size() != qpath.size())
{
    if(ppath.size() > qpath.size())
        ppath.pop();
    else
        qpath.pop();
}

第二步,让长链表上的快指针和短链表上的慢指针一起走,直到遇到相等节点。在这里,将两栈里面元素pop,直到两栈顶元素相等。

while(ppath.top() != qpath.top())
{
    ppath.pop();
    qpath.pop();
}
return ppath.top();

示例代码如下:

bool Getpath(TreeNode* root, TreeNode* n, stack<TreeNode*>& path)
{
    if(root == nullptr) return false;
    // 将路过的节点都入栈
    path.push(root);
    if(root == n) return true;
    // 没找到就继续往下找
    if(Getpath(root->left, n, path)) return true;
    if(Getpath(root->right, n, path)) return true;
    // 左右子树都遍历完了都没找到
    path.pop();
    return false;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    stack<TreeNode*> ppath, qpath;
    Getpath(root, p, ppath);
    Getpath(root, q, qpath);
    while(ppath.size() != qpath.size())
    {
        if(ppath.size() > qpath.size())
            ppath.pop();
        else
            qpath.pop();
    }
    while(ppath.top() != qpath.top())
    {
        ppath.pop();
        qpath.pop();
    }
    return ppath.top();
}
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值