2021.10.9 力扣-二叉树的最近公共祖先

目录

题目链接: 236. 二叉树的最近公共祖先 - 力扣(LeetCode)

题目描述:

方法一(递归法):

方法二(非递归后序遍历法)


题目链接: 236. 二叉树的最近公共祖先 - 力扣(LeetCode)

题目描述:

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

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

方法一(递归法):

 class Solution {
 public:
     TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
         //如果为空值,直接返回
         if (root == nullptr) return root;
         //只要找到一个节点是p或者q就可以直接返回
         if (root == p || root == q) return root;
         //如果curleft或curright非空,说明找到了p或者q或者题目所要求的最近公共祖先节点
         //也有可能是curleft和curright都非空,这说明什么节点都没找到,返回nullptr
         TreeNode* curleft = lowestCommonAncestor(root->left, p, q);
         TreeNode* curright = lowestCommonAncestor(root->right, p, q);
         //如果左子树和右子树中存在着p和q,说明当前的root就是最近的公共祖先
         if (curleft != nullptr && curright != nullptr) return root;
         //如果有一边的子树不为空,另一边的子树为空,就返回不为空的子树
         else if (curleft != nullptr) return curleft;
         else return curright;    //最后这一种情况,也包含了两边的子树都为空的情况,那么这一句一样可以返回nullptr
     }
 };

这题想了好久还是不会,思路是有思路,但是一些细节问题如何解决想了好久都没有办法,最后还是看了题解,看完题解的感觉就是这道题必须对树有一定的理解才行,看上去简单、实际解题时难、最后看完题解又有恍然大悟原来如此的感觉!

该题解的思路为:若节点root是p或者q就可以直接返回root;若root的左子树和右子树中存在着p和q,就说明root是最近的公共祖先,返回root;若root有一个子树为空,就返回另外一个子树;若root的两个子树都为空,就返回nullptr。

以上的写法为何是正确的?以下图为例:

 ①如何保证找到的公共祖先一定是最近的公共祖先?

        例如我们要在上图中寻找6和4的公共祖先,那么在节点5可以知道5的左子树中存在6,右子树中存在4,于是返回公共祖先5;注意,对于上层的根节点来说,p和q以及他们的公共祖先一定是在同一棵树上,即对于图中的3而言,因为6、4、5都在其左子树上,所以其左子树返回的是非空节点,而显然3的右子树必定不存在p或者q,所以其右子树返回的肯定是nullptr。因此依据以下这两条语句,能够保证找到的公共祖先一定是最近的公共祖先:

         //如果有一边的子树为空,就直接返回另外一边
         if (!curleft) return curright;
         if (!curright) return curleft;

②假设p或者q其中一个就是公共节点,那么一旦遇到root==p || root==q就直接返回了,这样一定正确吗?

        例如我们要早上图中找2和4的公共祖先,当5递归其右子树时在4之前首先就遇到了2,由于以下这条语句就直接返回了2:

         //只要找到一个节点是p或者q就可以直接返回
         if (root == p || root == q) return root;

但是与问题①类似,对于上层的根节点来说,p和q以及他们的公共祖先一定是在同一棵树上,由于我们要寻找的2和4都在5的右子树中,所以5的左子树必定返回的是nullptr,而由于右子树返回的是2,所以说明2一定就是2和4的最近公共祖先。

方法二(非递归后序遍历法)

在非递归后序遍历中,当访问到某个节点时,栈中的所有元素均为该结点的祖先,利用这一特性可以解决寻找最近公共祖先的问题。

代码虽然看起来复杂,但逻辑不难,就是在非递归后序遍历的代码基础上,添加两个栈,然后在原先访问结点的地方进行操作。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        vector<TreeNode*> sta1, sta2;   //sta1作为主栈,sta2作为辅助栈,因为需要遍历栈中的所有值,所以这里的栈不能用stack
        int top1 = -1, top2 = -1;       //分别为sta1和sta2的栈顶指针
        bool flag = false;              //flag为true时表示找到了p或q的其中一个,为false时表示都未找到
        TreeNode* cur = root, * pre = nullptr;
        //非递归后序遍历
        while (cur != nullptr || top1 >= 0)
        {
            while (cur != nullptr)
            {
                sta1.push_back(cur);    //vector未声明大小时,只能用push_back添加元素
                top1++;
                cur = cur->left;
            }
            cur = sta1[top1];
            if (cur->right != nullptr && pre != cur->right)
            {
                cur = cur->right;
            }
            else
            {
                //第二次找到p或者q时(第二次的代码要放在第一次前面写,不然第一次的代码将flag变为了true之后就直接进入第二次的代码了)
                if ((cur == p || cur == q) && flag == true)
                {
                    //这时主栈中的序列和辅助栈中的序列分别是从根节点到达p和q的路径,也是他们的各自的祖先
                    //因此从后往前匹配,第一个匹配成功的结点就是最近公共祖先
                    for (int i = top1; i >= 0; i--)
                    {
                        for (int j = top2; j >= 0; j--)
                        {
                            if (sta1[i] == sta2[j])
                            {
                                return sta1[i];
                            }
                        }
                    }
                }
                //当第一次找到p或者q时
                if ((cur == p || cur == q) && flag == false)
                {
                    //将主栈中的元素都复制到辅助栈中
                    for (int i = 0; i <= top1; i++)
                    {
                        sta2.push_back(sta1[i]);
                        top2++;
                    }
                    flag = true;
                }
                //与普通的数组不同,这里还需要弹出旧元素,不然下次的push_back操作会依然将新元素放到旧元素后面
                sta1.pop_back();        
                top1--;
                pre = cur;
                cur = nullptr;
            }
        }
        return nullptr;
    }
};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值