二叉树:给定一棵二叉树的头节点head,和另外两个节点a和b,返回a和b的最低公共祖先-年长大叔的用心爱爱漫画

@[toc]

# 1. 题目描述

给定一棵二叉树的头节点head,和另外两个节点a和b,返回a和b的最低公共祖先。

# 2. 测试案例

* 树空

* 树不空

# 3. 最佳思路

## 3.1 笔试

![在这里插入图片描述](https://file.jqhtml5.com/file/view/20220929/nnteb2qxxmi.jpg)

```

import java.util.HashMap;

import java.util.HashSet;

import java.util.Map;

import java.util.Set;

public class Main {

    static class Node {

        public int value;

        public Node left;

        public Node right;

        public Node(int value) {

            this.value = value;

        }

    }

    public static void main(String[] args) {

        Node root = new Node(1);

        root.left = new Node(2);

        root.right = new Node(3);

        root.left.left = new Node(4);

        root.left.right = new Node(5);

        root.right.left = new Node(6);

        root.right.right = new Node(7);

        Node node = right(null, root, root.right.left);

        System.out.println(node == null ? node : node.value);

    }

    // 找到a和b的最小公共祖先

    public static Node right(Node root, Node a, Node b) {

        /* 0. 边界条件 */

        if (root == null)

            return null;

        /* 1. key=当前结点,value=当前结点的父结点。遍历二叉树,放入map中 */

        Map<Node, Node> map = new HashMap<>();

        fillMap(root, map);

        map.put(root, null); // 根结点没有父结点,记得为根结点添加null父结点

        /* 2. 将a的祖先结点放入一个set中*/

        Set set = new HashSet<>();

        Node parent = map.get(a);

        while (parent != null) {

            set.add(parent);

            parent = map.get(parent);

        }

        /* 3. 依次找b的祖先结点,并判断此结点是否在set中。若在,则找到最小公共祖先,若不在则不存在最小公共祖先 */

        parent = map.get(b);

        while (parent != null) {

            if (set.contains(parent))

                return parent;

            parent = map.get(parent);

        }

        return null;

    }

    public static void fillMap(Node cur, Map<Node, Node> map) {

        if (cur.left != null) {

            map.put(cur.left, cur);

            fillMap(cur.left, map);

        }

        if (cur.right != null) {

            map.put(cur.right, cur);

            fillMap(cur.right, map);

        }

    }

}

```

## 3.2 面试

假设当前结点为x结点。那么当x的左孩子包含a,右孩子包含b;或x的左孩子包含b,右孩子包含a时。此时的x结点就是最小祖先结点。只需要记录下来即可。

由于是采用二叉树的后序遍历解题,传参数并不能解决问题。有2个解决方法:

1. 设置一个全局变量用于保存结果。

2. 在Info类中添加一个字段用于保存信息。

选第二种较好,最终Info类有3个字段。

> 代码说明:

> ![在这里插入图片描述](https://file.jqhtml5.com/file/view/20220929/kjophk2hkj5.jpg)

```

public class Main {

    static class Node {

        public int value;

        public Node left;

        public Node right;

        public Node(int value) {

            this.value = value;

        }

    }

    static class Info {

        public boolean isContainA; // 是否包含a

        public boolean isContainB; // 是否包含b

        public Node minParent; // 最小公共祖先

        public Info(boolean isContainA, boolean isContainB, Node minParent) {

            this.isContainA = isContainA;

            this.isContainB = isContainB;

            this.minParent = minParent;

        }

    }

    public static void main(String[] args) {

        Node root = new Node(1);

        root.left = new Node(2);

        root.right = new Node(3);

        root.left.left = new Node(4);

        root.left.right = new Node(5);

        root.right.left = new Node(6);

        root.right.right = new Node(7);

        Node node = findMinParent(null, root.left.left, root.right.left);

        System.out.println(node == null ? node : node.value);

    }

    // 找到a和b的最小公共祖先

    public static Node findMinParent(Node root, Node a, Node b) {

        return pos(root, a, b).minParent;

    }

    // 利用二叉树的后序遍历解题

    public static Info pos(Node cur, Node a, Node b) {

        /* 0.边界条件 */

        if (cur == null)

            return new Info(false, false, null);

        /* 1. 获取左右孩子的信息 */

        Info leftInfo = pos(cur.left, a, b);

        Info rightInfo = pos(cur.right, a, b);

        /* 2. 实例化当前结点的info对象,将其属性设置正确,最后返回 */

        // (1) 实例化info对象

        Info info = new Info(false, false, null);

        // (2) 设置info的isContainA与isContainB属性值

        info.isContainA = leftInfo.isContainA || rightInfo.isContainA;

        info.isContainB = leftInfo.isContainB || rightInfo.isContainB;

        // (3) 设置info的minParent属性值

        if (info.isContainA && info.isContainB)

            info.minParent = cur;

        info.isContainA = info.isContainA || cur == a;

        info.isContainB = info.isContainB || cur == b;

        // 保证后序结点能够正确保存结果

        if (leftInfo.minParent != null)

            info.minParent = leftInfo.minParent;

        if (rightInfo.minParent != null)

            info.minParent = rightInfo.minParent;

        // (4) 返回info对象

        return info;

    }

}

```

# 4. 代码

```

import java.util.*;

public class Main {

    static class Node {

        public int value;

        public Node left;

        public Node right;

        public Node(int value) {

            this.value = value;

        }

    }

    static class Info {

        public boolean isContainA; // 是否包含a

        public boolean isContainB; // 是否包含b

        public Node minParent; // 最小公共祖先

        public Info(boolean isContainA, boolean isContainB, Node minParent) {

            this.isContainA = isContainA;

            this.isContainB = isContainB;

            this.minParent = minParent;

        }

    }

    public static void main(String[] args) {

        int testCount = 1000000; //测试次数

        int maxHeight = 100; // 创建的最大树高

        int maxValue = 100; // 树结点的最大值

        boolean isSucceed = true;

        for (int i = 0; i < testCount; i++) {

            // 随机创建一颗二叉树

            Node root = randomCreatBT(maxHeight, maxValue);

            // 随机找到二叉树的两个最底部的结点

            Node a = randomFindBTNode(root);

            if (a == null) // 若a == null,说明root == null或只有一个结点,那么a,b无最小公共结点

                continue;  // 且更加重要的是会导致下面第二行while陷入死循环,故要使用continue跳过

            Node b = null;

            while ((b = randomFindBTNode(root)) == a) ; // 保证a与b不是同一个结点

            // 使用两种方式分别获取结果,并比较是否相同

            Node ans1 = right(root, a, b);

            Node ans2 = findMinParent(root, a, b);

            if (ans1 != ans2) {

                printBT(root, 0, 20, "根:");

                System.out.println("结点a:" + a.hashCode() + " " + a.value);

                System.out.println("结点b:" + b.hashCode() + " " + b.value);

                System.out.println("right:" + (ans1 == null ? "null" : (ans1.hashCode() + " " + ans1.value)));

                System.out.println("findMinParent:" + (ans2 == null ? "null" : (ans2.hashCode() + " " + ans2.value)));

                isSucceed = false;

                break;

            }

        }

        System.out.println(isSucceed ? "nice!" : "fuck!");

    }

    // 随机创建一个二叉树

    public static Node randomCreatBT(int maxHeight, int maxValue) {

        return creatNode(maxHeight, maxValue, 1); // 确定了要生成的树高

    }

    public static Node creatNode(int maxHeight, int maxValue, int curHeight) {

        Node cur = null;

        if (Math.random() > 0.5 && curHeight <= maxHeight) {

            cur = new Node((int) (Math.random() * (maxValue + 1)));

            cur.left = creatNode(maxHeight, maxValue, curHeight + 1);

            cur.right = creatNode(maxHeight, maxValue, curHeight + 1);

        }

        return cur;

    }

    // 打印一颗二叉树

    public static void printBT(Node cur, int height, int len, String flag) {

        if (cur == null)

            return;

        // 右

        printBT(cur.right, height + 1, len, "R:");

        // 根

        printNode(cur, height, len, flag);

        // 左

        printBT(cur.left, height + 1, len, "L:");

    }

    public static void printNode(Node cur, int height, int len, String flag) {

        StringBuffer str = new StringBuffer(len * (height + 1));

        // 前空格

        for (int i = 0; i < height * len; i++)

            str.append(" ");

        String valueStr = flag + cur.hashCode() + " " + cur.value;

        int leftSpaces = len - valueStr.length() >> 1;

        int rightSpaces = len - leftSpaces - valueStr.length();

        // 左空格

        for (int i = 0; i < leftSpaces; i++)

            str.append(" ");

        // 值

        str.append(valueStr);

        // 右空格

        for (int i = 0; i < rightSpaces; i++)

            str.append(" ");

        System.out.println(str);

    }

    // 随机找到二叉树的某个结点

    public static Node randomFindBTNode(Node root) {

        /* 0. 边界条件 */

        if (root == null || (root.left == null && root.right == null))

            return null;

        /* 1. 采用某种方式遍历树,并将结果放入list中。(这里采用先序遍历) */

        List<Node> list = new LinkedList<>();

        pre(root, list);

        /* 2. 从list中随机选出一个下标,并返回该下标所在结点 */

        return list.get((int) (Math.random() * list.size()));

    }

    public static void pre(Node cur, List<Node> list) {

        if (cur == null)

            return;

        list.add(cur);

        pre(cur.left, list);

        pre(cur.right, list);

    }

    // 找到a和b的最小公共祖先

    public static Node findMinParent(Node root, Node a, Node b) {

        return pos(root, a, b).minParent;

    }

    // 利用二叉树的后序遍历解题

    public static Info pos(Node cur, Node a, Node b) {

        /* 0.边界条件 */

        if (cur == null)

            return new Info(false, false, null);

        /* 1. 获取左右孩子的信息 */

        Info leftInfo = pos(cur.left, a, b);

        Info rightInfo = pos(cur.right, a, b);

        /* 2. 实例化当前结点的info对象,将其属性设置正确,最后返回 */

        // (1) 实例化info对象

        Info info = new Info(false, false, null);

        // (2) 设置info的isContainA与isContainB属性值

        info.isContainA = leftInfo.isContainA || rightInfo.isContainA;

        info.isContainB = leftInfo.isContainB || rightInfo.isContainB;

        // (3) 设置info的minParent属性值

        if (info.isContainA && info.isContainB)

            info.minParent = cur;

        info.isContainA = info.isContainA || cur == a;

        info.isContainB = info.isContainB || cur == b;

        // 保证后序结点能够正确保存结果

        if (leftInfo.minParent != null)

            info.minParent = leftInfo.minParent;

        if (rightInfo.minParent != null)

            info.minParent = rightInfo.minParent;

        // (4) 返回info对象

        return info;

    }

    // 找到a和b的最小公共祖先

    public static Node right(Node root, Node a, Node b) {

        /* 0. 边界条件 */

        if (root == null)

            return null;

        /* 1. key=当前结点,value=当前结点的父结点。遍历二叉树,放入map中 */

        Map<Node, Node> map = new HashMap<>();

        fillMap(root, map);

        map.put(root, null); // 根结点没有父结点,记得为根结点添加null父结点

        /* 2. 将a的祖先结点放入一个set中*/

        Set set = new HashSet<>();

        Node parent = map.get(a);

        while (parent != null) {

            set.add(parent);

            parent = map.get(parent);

        }

        /* 3. 依次找b的祖先结点,并判断此结点是否在set中。若在,则找到最小公共祖先,若不在则不存在最小公共祖先 */

        parent = map.get(b);

        while (parent != null) {

            if (set.contains(parent))

                return parent;

            parent = map.get(parent);

        }

        return null;

    }

    public static void fillMap(Node cur, Map<Node, Node> map) {

        if (cur.left != null) {

            map.put(cur.left, cur);

            fillMap(cur.left, map);

        }

        if (cur.right != null) {

            map.put(cur.right, cur);

            fillMap(cur.right, map);

        }

    }

}

```

「Yeha (예하)」Pure Media Vol.239 [158P-275MB]
2023-05-31
概要原文中野サンプラザのライブを控え、身を引き締める一同。だが、蓮から「間違えない、でもそれだけ」と言われていた杏夏は、不安が拭えない。そんな彼女に春は「信じて。私たちと――マネージャー君を」とエールを送る。それを受け、杏夏もついに踏ん切りがついた。これまで関わった仲間やライバル、そしてファンが見守る中、直輝から授けられた「秘密兵器」を携えて、TINGSはステージへと踏み出す。そしてそれは、それぞれの目標に向かってのスタートでもあった――。翻译在中野Sunplaza的演唱会之前,大家都收紧了身体
2022-10-17
日本动画公司扳机社TRIGGER今日宣布将在Anime Expo 2023上公布《吊带袜天使》新企划、《天元突破:红莲螺岩》剧场版15周年以及《迷宫饭》等动画的最新消息,Anime Expo 2023是美国洛杉矶的动画展,今年预计将于7月1日至7月4日举办。
2023-06-09
由大宇资讯发行的《大富翁》系列游戏最新作《大富翁11》即将于2022年内上线PC与NS平台,官方继续公布游戏的新情报,本次主要介绍游戏的中的地图!《大富翁11》首发12张各具特色的地图,8张传统玩法地图4张热斗玩法地图,在异乡风情雪国踱步,到国风不夜天漫游;从色彩斑斓积木世界,到神秘深海探险;各大地图为满足玩家不同游玩节奏专门设计,各位富翁均可自由选择,那么哪张地图富翁,哪种玩法是您最喜欢呢?地图动画效果表现力也得到了提升。本作中部分地图加入了创新设计“机关”对各位富翁的策略与运气提出了双重挑战。闯入在机
2022-09-30
[pixiv] 插画今日排行榜 2023年5月28日
2023-05-29
《刀剑神域》动画化目前已经到第三季Alicization篇了,《刀剑神域Alicization》前半段于2018年10月-2019年3月播出,后半段“War of Underworld”预定于2019年10月放送。近日官网公布了《刀剑神域Alic...
2022-10-28
真人电影《狂赌之渊 走投无路的俄罗斯转盘》公开了开头的一部分内容。在这次的视频中,可以看到藤井流星饰演的视鬼神真玄,和松田瑠华饰演的原学生会干部皇伊月对战时的样子。
2022-10-28
年年 世界杯[30P/91M]
2023-03-08
Vol.188 潘娇娇[80P-682M]
2023-06-12
萌娘资源专业站点,致力于传播二次元文化,万物皆可萌
2023-05-21