二叉树的最近公共祖先

2 篇文章 0 订阅
1 篇文章 0 订阅

        今天在 leetcode 上面刷到这样一道题: 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

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

        有兴趣的小伙伴可以去 leetcode 上面做下这道题,力扣

        我想了挺久的,最后使用的方法还是比较复杂的,在这里贴出来我独立写出来的代码。

715d5535b0d60d1657030012edebfe3a.jpeg

        主要的思路就是使用遍历的方法获得 两个节点的路径(从根节点到目标节点过程中所经过的节点),然后比较出两条路径相同的部分,取相同部分的最后一个节点作为返回值。

        当然这样写比较麻烦,于是去看了看题解,找到一种更好的方法,经过我自己的消化,这里尽可能将这个好的方法逻辑清晰地描述出来。

        这里我们有一颗二叉树,根节点为 root 。我们要寻找的是 p 节点和 q节点的公共祖先。

8488cbdf220c75aeb14ce041f31788b0.jpeg

我们使用递归方法 f (root,p,q) 。

整体描述下该方法的作用:

        在 root 为根节点的树中寻找 p 和 q 的深度最大的公共祖先。

  • 如果有公共祖先,就返回深度最大的公共祖先。
  • 如果树中只有 p 或 q 节点中的一个,此时没有公共祖先,就只返回 p 或 q 节点。
  • 如果树中没有 p 也没有 q 节点,返回 null。

        题目中明确说明了树中有 p 和 q 两个节点,所以对题目中的root 节点调用该方法一定可以返回一个公共祖先。

首先考虑 f 方法中root参数可能的情形:

1. root 为 null。此时在 root 为根的二叉树中没有任何节点,所以返回值应该为 null。虽然 f 方法对 root 的描述表示 root 可能为 null,但是在题目中,我们第一次调用时 root 必然不可能为 null,因为题目的树中至少有 p 和 q 两个节点。只有在递归调用时调用到叶子节点后,再次递归调用才会出现1这种情况。

2. root==p。此时 p 作为根节点,已经是最顶级的节点了,如果树中没有q,根据上面的描述,因为没有公共祖先,我们要返回 p。即使树中有 q ,我们需要返回公共祖先,那么公共祖先也只可能是 p。所以此时可以直接返回 p ,符合方法的描述。

3. root==q。此时 q 作为根节点,已经是最顶级的节点了,如果树中没有p,根据上面的描述,因为没有公共祖先,我们要返回q。即使树中有 p ,我们需要返回公共祖先,那么公共祖先也只可能是 q。所以此时可以直接返回 q ,符合方法的描述。

4. root != q 且 root != p。先说下结论,下面详细说明。对左右子树调用 f 方法,如果均返回 null,说明整棵树不包含 p 或者 q,所以返回 null; 如果只有一个子树有返回值,说明该子树只有 p 或者 q,该子树f 方法返回 p 和 q 中的一个,或者有 p 和 q,该子树f 方法返回公共祖先,所以 root 的f 方法此时返回该子树的返回值; 如果两个子树都有返回值,说明p和q分布在左右子树各一个,他们的公共祖先为root,此时返回 root。

        第4种情形比较复杂,我们仔细讨论下。如果方法传进来的 root 不是 p 也不是 q。 那么我们就可以考虑 root 的左右子树的各种情况。

4.1 左子树不包含 p、q 任一节点,右子树包含 p或q 一个节点。对右子树调用 f 方法返回 p或q,对左子树调用f 方法返回null。此时方法应该返回右子树的返回值。

4.2 左子树包含 p 或 q 一个节点,右子树不包含 p、q 两个节点。对左子树调用 f 方法返回 p 或 q,对右子树调用f 方法返回null。此时方法应该返回左子树的返回值。

4.3 左子树不包含 p、q 任一节点,右子树包含 p、q 两个节点。对右子树调用 f 方法返回 p、q 的深度最大的公共祖先,对左子树调用 f 方法返回 null。此时方法应该返回右子树的返回值。

4.4 左子树包含 p、q 两个节点,右子树不包含 p、q 两个节点。对左子树调用 f 方法返回 p、q 的深度最大的公共祖先,对右子树调用f 方法返回 null。此时应该返回左子树的返回值。

4.5 左子树包含 p 节点,右子树包含 q 节点。对左子树调用 f 方法返回 p 节点,对右子树调用 f 方法返回 q 节点。此时应该返回 root 节点,因为 p 和 q分布在 root 的两个子树中。

4.6 左子树包含 q 节点,右子树包含 p 节点。对左子树调用 f 方法返回 q节点,对右子树调用 f 方法返回 p 节点。此时应该返回 root 节点,因为 p 和 q分布在root的两个子树中。

4.7 左子树不包含p、q 任一节点,右子树不包含 p、q 任一节点。对左右子树调用 f方法均得到 null。

        我们用表格来表示这几种情形:

4f91d5faa5caa7192e7858f415aa910d.jpeg

        现在来看看我们对题目的根节点调用  f  方法的情形吧(只看第一层调用)。题目说树中有 p 和 q 两个节点,则对题目的 root 调用f 方法只会进入 2、3、4 三种情形,2、3 容易处理,如果是 4,我们调用左右子树,则会得到到 4.3、4.4、4.5、4.6这四种,然后根据表格的四种处理办法返回对应的值即可。

下面是 f 函数的实现:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null||root==p||root==q)return root;//2、3情形

        //处理 4 情形
        TreeNode left=lowestCommonAncestor(root.left,p,q);//调用左子树
        TreeNode right=lowestCommonAncestor(root.right,p,q);//调用右子树
        
        if(left==null)return right;//4.1情形
        if(right==null)return left;//4.2情形
        return root;//4.3、4.4情形
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

m0_37657276

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值