请你寻找二叉树中任意俩节点x和y的最低公共祖先

请你寻找二叉树中任意俩节点x和y的最低公共祖先

提示:本节仍然是重点说二叉树的DP递归套路,非常重要而且容易理解

二叉树的动态规划树形DP递归套路系列文章有这些,可以帮助你快速掌握树形DP的题目解题思想,就一个套路:
(1)判断二叉树是否为平衡二叉树?树形DP,树形动态规划的递归套路
(2)求二叉树中,任意两个节点之间的距离最大值是多少
(3)求二叉树中,包含的最大二叉搜索子树的节点数量是多少
(4)求二叉树中,包含的最大二叉搜索子树的头节点是谁,它包含的节点数量是多少
(5)求公司派对的最大快乐值
(6)判断二叉树是否为满二叉树
(7)判断二叉树是否为完全二叉树


题目

给你一个二叉树head,请你寻找节点x和节点y的最低公共祖先
所谓最低公共祖先:
就是x和y往上寻找父节点的过程中,第一次相遇的那个节点
换句话说,就是一个节点cur,它是第一个节点,以自己开头的树上,同时已经有x和y节点


一、审题

示例:下列x和y的最低公共祖先是:cur
在这里插入图片描述
下列x和y节点的最低公共祖先是A
在这里插入图片描述
本题用的二叉树:

public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int v) {
            value = v;
        }
    }

    //构造一颗树,今后方便使用
    public static ArrayList<Node> generateBinaryTree() {
        //树长啥样呢
        //          1
        //        2   3
        //       4 5 6 7
        //      8
        //     9
        Node head = new Node(1);
        Node n2 = new Node(2);
        Node n3 = new Node(3);
        head.left = n2;
        head.right = n3;
        Node n4 = new Node(4);
        Node n5 = new Node(5);
        n2.left = n4;
        n2.right = n5;
        Node n6 = new Node(6);
        Node n7 = new Node(7);
        n3.left = n6;
        n3.right = n7;
        Node n8 = new Node(8);
        n4.left = n8;
        Node n9 = new Node(9);
        n8.left = n9;
        ArrayList<Node> res = new ArrayList<>();
        res.add(head);
        res.add(n2);
        res.add(n9);

        return res;
    }

二、树形动态规划的递归套路:树形DP

95%的二叉树动态规划问题,都可以用以下套路解决:
一、定义个信息类:Info
收集x左树和右树都有的信息(左右树都有哦,而不是针对某单独的左树或者右树),比如是否平衡?树的高度,总之就是有的信息,不管你是String还是Boolean还是Integer类型的信息。经常是要讨论与x有关,还是 与x无关。

二、树形DP递归套路: 来到x节点
1)base case:考虑叶节点应该返回的信息Info
2)先收集左树和右树的信息Info
3)综合2)整理x自己的信息,并返回;

三、从题目给的二叉树head开始求(2),得到了一个head的信息Info,找里面咱们要用的那个信息:比如是否平衡?返回它。

来,咱们举例说明:实践知真理!


三、最优解:树形DP解决最低公共祖先,笔试面试均可用

要深刻理解最低公共祖先的含义:它是第一个节点,以自己开头的树上,同时已经有x和y节点

95%的二叉树动态规划问题,都可以用以下套路解决:
一、定义个信息类:Info
收集h左树和右树都有的信息(左右树都有哦,而不是针对某单独的左树或者右树),比如是否平衡?树的高度,总之就是有的信息,不管你是String还是Boolean还是Integer类型的信息。经常是要讨论与x有关,还是 与x无关。
如果我们去x开头的树,下面要啥信息呢?

(1)h这个树,包含的x和y的最低公共祖先是谁?ancestor,这本就是本题要求的
(2)咋找才能说明ancestor找到了呢,至少,在你这个节点x这里,我们同时已经找到了x节点,也找到了y节点
在这里插入图片描述
比如,A之所以是x和y的最低公共祖先,就是因为A是第一个点,满足,在A这个子树上,同时已经找到了x节点,也同时找到了y节点。

故,咱们还需要在树上保留这俩信息:hasFindXhasFindY
信息类可以这么定义:

public static class Info{
        public Node ancestor;//找到祖先了吗
        public boolean xisFind;
        public boolean yisFind;
        public Info(Node a, boolean x, boolean y){
            ancestor = a;
            xisFind = x;
            yisFind = y;
        }
    }

二、树形DP递归套路: 来到x节点
定义函数f(Node h),用来收集x这个树上的信息:

1)base case:考虑叶节点应该返回的信息Info
遇到叶节点的左右子为null
这个时候,祖先肯定是null,没找到呢,null不管用
找到了x吗?hasFindX=false;
找到了y吗?hasFindX=false;

if (K == null) return new Info(null, false, false);//没xy,没有祖先

2)先收集左树和右树的信息Info
好说:

        //左边收信息,右边收信息
        Info leftInfo = process(K.left, x, y);
        Info rightInfo = process(K.right, x, y);

3)综合2)整理x自己的信息,并返回;
——整理找到了x吗?
如果h就是x或者,h的左边找到了x,或者h的右边找到了x,算是x找到了
——整理找到了y吗?
如果h就是y,或者h的左边找到了y,或者h的右边找到了y,算是x找到了

——整理ancestor是谁呢?
注意,如果h左边已经找到了ancestor,那就是它,用左边的替代,当初那个点就是最低公共祖先,你现在h是也没用,因为h不是最低的。
当然如果右边先找到了,右边的那个最低公共祖先,保持不变
啥时候h就是最低公共祖先呢?
第一保证ancestor到目前位置,还是null没找到,且
第二,左边找到了x,右边找到了y,或者左边找到了y右边找到了x【不可能同时找到,否则最低公共祖先上面就有了】,又或者,左边找到了x,h=y,又或者,右边找到了x,h=y,又或者左边找到了y,h=x,又或者右边找到了y,h=x
扯这么多没用……
第二点一句话概括,即同时找到了x,又找到了y(xisFind && yisFind)
如果上面第一第二都满足,则h当前就是ancestor,更新,从今往后就是这一个结果,伴随到最初的定点。

这么一分析,真的非常强大,这个树形DP的解题思路,可以轻松地解决这个题
代码很简单:

//复习:收集x树的信息
    public static Info f(Node head, Node x, Node y){
        //去找head这个树上x与y的最低公共祖先是谁?
        if (head == null) return new Info(null, false, false);

        //左右收集信息
        Info left = f(head.left, x, y);
        Info right = f(head.right, x, y);

        //整理信息y
        boolean xIsFind = head == x || left.xisFind || right.xisFind;
        boolean yIsFind = head == y || left.xisFind || right.xisFind;

        Node ancestor = null;
        if (left.ancestor != null) ancestor = left.ancestor;
        if (right.ancestor != null) ancestor = right.ancestor;//左右已经有了,那算了
        //仍然没有找到,则看看head是不是可能成为ancestor
        if (ancestor == null && xIsFind && yIsFind) ancestor = head;
        //只要此刻x与y同时已经找到了,不管在哪,head就是ancestor

        return new Info(ancestor, xIsFind, yIsFind);
    }

三、从题目给的二叉树head开始求(2),得到了一个head的信息Info,找里面咱们要用的那个信息:比如是否平衡?返回它。
调用嘛好说 ,就找head的信息,返回里面的ancestor

//调用很简单
    public static Node ancestorOfXY(Node head, Node x, Node y){
        if (head == null) return null;

        Info info = f(head, x, y);
        return info.ancestor;
    }

    //树长啥样呢
    //          1
    //        2   3
    //       4 5 6 7
    //      8
    //     9
    //x = 9, y=5
    public static void test2(){
        ArrayList<Node> arrayList = generateBinaryTree();//生成我们要找到节点
        Node cur = findLowestPublicNode2(arrayList.get(0),
                arrayList.get(1),
                arrayList.get(2));
        System.out.println(cur.value);
    }

    public static void main(String[] args){
        test2();
    }

结果:

2

很容易吧,不管面试还是笔试,看到这个题,那就很简单的。


四、笔试次优解,可以AC,但是耗费额外空间,不太好

最直白的做法,就是去x和y同时往上找,看看寻找路径第一次碰面在哪个节点?
这就需要寻找父节点了,可是
咱得二叉树,没有parent指针,那咋知道x的父节点呢?
用哈希表存,然后遍历每一个二叉树的点x,把x左子右子左key,x左value,放在哈希表中
在这里插入图片描述

//遍历二叉树,填表,各个节点的父节点
    public static void fillParentMapReview(Node head, HashMap<Node, Node> map){
        if (head.left == null && head.right == null) return;//不管叶节点

        if (head.left != null){//左边有左子,就可以给左子上表
            map.put(head.left, head);
            fillParentMapReview(head.left, map);//DFS遍历玩
        }
        if (head.right != null){//有右子,给右子上表,然后DFS遍历玩
            map.put(head.right, head);
            fillParentMapReview(head.right, map);//先天他们,再来填我--随意
        }
    }

然后,咱们开始寻找
有了x和y地址,那,先把x的父节点,一条线,沿途父节点全部加入集合set中
从y开始找,沿途父节点,去set对比,第一个set中出现的与x共父节点的那个点,就是最低公共祖先

这个思想很简单,无非就是耗费时间和额外空间。
手撕代码:

//复习,用哈希表存父节点,x父节点沿途加入set,然后沿途找y的父节点,看看谁先出现在set中
    public static Node hashMapFindAncestorOfXY(Node head, Node x, Node y){
        if (head == null) return null;
        if (x == y) return x;//本来就相同,何必呢

        HashMap<Node, Node> map = new HashMap<>();
        //遍历二叉树,填表
        fillParentMapReview(head, map);

        //有了父节点表,那就可以先把x的父节点路径搞到set中
        HashSet<Node> set = new HashSet<>();
        set.add(x);//x也在里面
        Node cur = map.get(x);//拿到x的父节点
        while (cur != null){
            //有父节点,加进来
            set.add(cur);
            cur = map.get(cur);//继续上窜
        }

        //沿途找y的父节点,看看第一个在set中的那个点,就是最低公共祖先
        cur = map.get(y);
        while (!set.contains(cur)){
            //沿途父节点,一直不在set中,继续上窜
            cur = map.get(cur);
        }
        //一旦发现set有了cur了,cur即最低公共祖先

        return cur;
    }

测试一把:

//树长啥样呢
    //          1
    //        2   3
    //       4 5 6 7
    //      8
    //     9
    //x = 9, y=5
    public static void test2(){
        ArrayList<Node> arrayList = generateBinaryTree();//生成我们要找到节点
        Node cur = findLowestPublicNode2(arrayList.get(0),
                arrayList.get(1),
                arrayList.get(2));
        System.out.println(cur.value);

        cur = ancestorOfXY(arrayList.get(0),
                arrayList.get(1),
                arrayList.get(2));
        System.out.println(cur.value);//复习:树形DP实现

        cur = hashMapFindAncestorOfXY(arrayList.get(0),
                arrayList.get(1),
                arrayList.get(2));
        System.out.println(cur.value);//复习哈希表实现的
    }

    public static void main(String[] args){
        test2();
    }

结果OK

2
2
2

总结

提示:重要经验:

1)树形DP的递归套路,说了多少次,非常非常重要,而且形式简单,这是破解95%二叉树的动态规划题目的,一定要熟悉
2)此题,理解最低公共祖先的关键,在于第一次在某个点为头的树中,同时找到了x和y节点,这使得整理信息,变成了一句话。
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

冰露可乐

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

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

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

打赏作者

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

抵扣说明:

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

余额充值