【二叉树】最近公共祖先

最近公共祖先

题目

题目链接: 二叉树的最近公共祖先

思路讲解一

我们的第一个方法就是简单的分类讨论, 我们首先来看下面当最近公共祖先为根节点的情况
在这里插入图片描述

此时我们可以得到两种情况:

  1. 当左右子树都没有最近公共祖先时/两个节点位于根节点左右两侧时, 最近公共祖先在根节点上
  2. 当有一个节点位于根节点时, 最近公共祖先在根节点上

然后我们来看如果最近公共祖先不是根节点的情况

在这里插入图片描述

实际上, 这两个情况本质上是可以看作同一种情况的, 也就是: 当两个节点位于同一个子树上时, 最近公共祖先在此子树上面.

并且对于最近公共祖先位于子树上面的情况, 我们可以通过递归将其转换为前面的两个情况, 如下

在这里插入图片描述

那么此时我们就可以确定, 这个题目是可以通过递归来实现的, 因为向下递归后子问题的情况和主问题一致.

总结一下上面的思路讲解:

  1. 当两个节点位于根两侧时, 最近公共祖先为根节点
  2. 当两个节点位于同侧时, 最近公共祖先处于同侧子树上
  3. 当有一个节点位于根时, 最近公共祖先为根节点

我们就依旧是二叉树的深搜套路. 先根据题目要求,设定函数返回值为返回两个节点的最近公共祖先, 但是此时我们只需要简单的进行分析, 就会发现这个返回值根本不够用. 因为如果我们设定返回最近公共祖先, 那么下面的这两个情况我们根本就无法区分了

在这里插入图片描述

也就是说, 我们的返回值设定, 实际上是有问题的. 换句话说, 返回的信息应该还需要能够代表一些信息, 从而能够区分上面这样的情况. 并且这个返回值应该提供能够让我去确定, 当前节点是否是一个最近公共祖先的信息.

那么除了让返回值能够代表最近公共祖先以外, 还有什么方式可以确定最近公共祖先呢? 此时我们可以会想到我们最开始的分析, 我们是如何分析出最近公共祖先的位置的呢? 答案就是根据 p 和 q 节点的位置, 那么此时我们就可以设定返回值为 p 节点或 q 节点的存在情况.

那么此时这个返回值就非常的巧妙了, 我们来看看为什么. (下面是对返回值正确性的一些说明, 如果你没有疑问, 可以直接到最下方的深搜分析)

经过我们最开始的分析可以发现, p 和 q 要么这个节点在左右两侧, 要么一个在根, 要么两个同侧.

我们先看左右两侧的情况, 如果是左右两侧, 那么在最近公共祖先位置, 左右的返回值肯定不会是空, 这个时候就可以认定这个位置是最近公共祖先了.

在这里插入图片描述

此时可能有人要问了: 那此时你向上递归, 有没有可能会改变你的这个祖先的值呢?

实际上是不可能的, 因为我们的返回值是 p 和 q 的存在情况, 当我们确认公共祖先后, 证明 p 和 q 一定在刚刚那个公共祖先的位置以及它的下面, 你往上递归, 是不可能遇到其他的 p 和 q 的存在情况的.

在这里插入图片描述

接下来就是第二个, 一个节点在根的情况.

此时可能有人要问了: 那么如果 p 或者 q 节点在根, 那此时我们应该是直接返回当前节点的存在情况, 那么这样如果 q 在下面是否会被忽略呢?

想必问出这样问题的人, 应该是纠结于下面的这个情况, 因此我们进行分析

在这里插入图片描述

此时直接返回会有问题吗? 答案是, 也是不会的, 因为如果 q 在右边, 自然会像上面的情况一样, 在后面把信息传递过来, 如下图所示

在这里插入图片描述

如果没有传递过来 q 的位置信息, 此时就能够充分的证明, 这个 q 一定在我的下面, 而不是在我的对面. 因为如果出现在对面, 我一定是可以在上面的某个节点处获取到的. 这个间接的排除也是非常的巧妙的.

最后一个情况则是两个同侧的情况, 实际上递归下去就是上面两个情况, 如果还不理解, 可以多画几个图来自行理解.

那么我们就设定返回值为 p 和 q 节点的存在状况, 然后进行深搜的分析

  1. 根节点处理: 如果根节点等于 p 或者 q, 返回根节点
  2. 左子树处理: 递归左子树找 p 和 q
  3. 右子树处理: 递归右子树找 p 和 q
  4. 边界条件: 为空返回空, 没找到
  5. 返回值: 如果左右子树找到了, 那么返回当前根节点. 如果只有一边找到了, 返回找到的一边

代码书写一

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        // 边界条件
        if(root == null) return null;
        // 根节点处理
        if(root == p || root == q) return root;
        // 左子树处理
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        // 右子树处理
        TreeNode right = lowestCommonAncestor(root.right, p, q);

        // 返回值
        if(left != null && right != null){
            return root;
        }else { 
            return left != null ? left : right;
        }
    }
}

思路讲解二

看作反向的链表相交, 利用深搜 + 栈存储遍历路径, 随后通过栈来找到第一个相交节点. 如下图

在这里插入图片描述

深搜思路:

设定返回值为是否找到对应路径

  1. 处理根节点: 把根节点放入栈, 查看是否是目标节点, 是返回 true , 不是返回 false
  2. 处理左子树: 搜索左子树
  3. 处理右子树: 搜索右子树
  4. 边界条件: 如果为空 返回false
  5. 返回值: 如果左右子树中有一个找到了, 直接返回 true, 否则出栈返回 false

代码书写二

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        Stack<TreeNode> s1 = new Stack<>();
        Stack<TreeNode> s2 = new Stack<>();

        getPath(root, p, s1);
        getPath(root, q, s2);
        // 将两个栈的节点数弄到相同
        while (s1.size() > s2.size()) {
            s1.pop();
        }
        while (s2.size() > s1.size()) {
            s1.pop();
        }

        // 找到第一个相同节点
        while (s1.peek() != s2.peek()) {
            s1.pop();
            s2.pop();
        }

        return s1.peek();
    }

    public boolean getPath(TreeNode root, TreeNode target, Stack<TreeNode> stack) {
        // 边界条件
        if (root == null) {
            return false;
        }

        // 处理根节点
        stack.push(root);
        if (root == target) return true;

        // 处理左子树
        boolean left = getPath(root.left, target, stack);
        // 返回值, 提前检测, 稍微减少一些开销
        if (left) return true;
        
        // 处理右子树
        boolean right = getPath(root.right, target, stack);
        // 返回值
        if (right) return true;

        // 都没有找到, 出栈, 返回false
        stack.pop();
        return false;
    }
}
  • 25
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值