• 算法练习(14)-二叉树中2个节点的最近公共祖先?


    比如这颗树,给定2个节点: 4、5 ,它们的最近公共祖先节点为2。类似的,如果是3、5,它们的最近公共祖先节点为1。

    一种比较容易想到的思路,如果知道每个节点到root的全路径, 比如

    3到root节点的全路径为: 3->1

    5到root节点的全路径为: 5->2->1

    这样,只要遍历对比下全路径, 就能知道最近的公共节点是1,求每个节点到根节点全路径的方法,在以前的文章算法练习(11)-二叉树的各种遍历 有详细代码,此处直接复用即可。

    /**
         * 获取每个节点到根节点的全路径
         *
         * @param node
         * @return
         */
        public static Map<TreeNode, List<TreeNode>> getToRootPath(TreeNode node) {
            if (node == null) {
                return null;
            }
            //记录每个节点->父节点的1:1映射
            Map<TreeNode, TreeNode> parentMap = new HashMap<>();
    
            Queue<TreeNode> queue = new LinkedList<>();
            queue.add(node);
            parentMap.put(node, null);
            while (!queue.isEmpty()) {
                TreeNode n = queue.poll();
                if (n.left != null) {
                    queue.add(n.left);
                    parentMap.put(n.left, n);
                }
                if (n.right != null) {
                    queue.add(n.right);
                    parentMap.put(n.right, n);
                }
            }
    
            //根据parentMap,整理出完整的到根节点的全路径
            Map<TreeNode, List<TreeNode>> result = new HashMap<>();
            for (Map.Entry<TreeNode, TreeNode> entry : parentMap.entrySet()) {
                TreeNode self = entry.getKey();
                TreeNode parent = entry.getValue();
    
                //把当前节点,先保护起来
                TreeNode temp = self;
                List<TreeNode> path = new ArrayList<>();
                while (parent != null) {
                    path.add(self);
                    self = parent;
                    parent = parentMap.get(self);
                    if (parent == null) {
                        path.add(self);
                    }
                }
                result.put(temp, path);
            }
            return result;
        }
    
    
        /**
         * 求节点n1,n2的最近公共祖先
         * @param root
         * @param n1
         * @param n2
         * @return
         */
        public static TreeNode getNearestSameParentNode(TreeNode root, TreeNode n1, TreeNode n2) {
            if (n1 == null || n2 == null) {
                return n1 == null ? n2 : n1;
            }
            if (n1.equals(n2)) {
                return n1;
            }
    
            Map<TreeNode, List<TreeNode>> path = getToRootPath(root);
            List<TreeNode> n1Path = path.get(n1);
            List<TreeNode> n2Path = path.get(n2);
            for (TreeNode node1 : n1Path) {
                for (TreeNode node2 : n2Path) {
                    if (node1.equals(node2)) {
                        return node1;
                    }
                }
            }
    
            return null;
        }
    
        public static void main(String[] args) {
    
            TreeNode n1 = new TreeNode(1);
            TreeNode n2_1 = new TreeNode(2);
            TreeNode n2_2 = new TreeNode(3);
            TreeNode n3_1 = new TreeNode(4);
            TreeNode n3_2 = new TreeNode(5);
            TreeNode n4_1 = new TreeNode(6);
            TreeNode n4_2 = new TreeNode(7);
    
            n1.left = n2_1;
            n1.right = n2_2;
    
            n2_1.left = n3_1;
            n2_1.right = n3_2;
    
            n2_2.left = n4_1;
            n2_2.right = n4_2;
            
            TreeNode result1 = getNearestSameParentNode(n1, n4_1, n4_2);
            System.out.println(n4_1 + "/" + n4_2 + " : " + result1);
    
            TreeNode result2 = getNearestSameParentNode(n1, n2_2, n3_2);
            System.out.println(n2_2 + "/" + n3_2 + " : " + result2);
    
            TreeNode result3 = getNearestSameParentNode(n1, n2_1, n3_1);
            System.out.println(n2_1 + "/" + n3_1 + " : " + result3);
        }
    

    输出:

    6/7 : 3
    3/5 : 1
    2/4 : 2
    

    这个方法,虽然思路很容易理解 ,但效率并不高, 额外空间借助了2个Map,空间复杂度至少也是O(N)级别,然后再做2轮循环比较全路径,时间复杂度也可以优化,事实上有更好的解法:

        public static TreeNode getNearestSameParentNode(TreeNode root, TreeNode n1, TreeNode n2) {
            if (root == null || root == n1 || root == n2) {
                return root;
            }
    
            TreeNode left = getNearestSameParentNode(root.left, n1, n2);
            TreeNode right = getNearestSameParentNode(root.right, n1, n2);
    
            if (left != null && right != null) {
                return root;
            }
    
            return left != null ? left : right;
        }
    

    这个代码很短, 但不太好理解 , 先分析下一颗树中的2个节点X、Y,它们最近公共祖先的情况:

    只会出现这2类情况:

    1、节点X在Y的某1侧子树中(反过来也一样, Y出现在X的某1侧子树中),即:1个节点就是另1个的最近公共祖先。

    2、节点X与Y,必须向上汇聚, 才能走到1个最近的交叉节点

    在优化版的代码中,使用了递归求解。

    第1段if判断,其实有2层意思,用于处理递归过程中的边界返回:

            if (root == null || //递归到最末端叶节点时的函数返回
                 (root == n1 || root == n2) //在左右子树遍历过程中,如果发现当前节点就是n1或n2,直接返回,因为下面的子节点,肯定不可能再是它俩的公共祖先节点了
            ) {
                return root;
            }
    

    最后1段return,表示遍历过程中,如果X或Y只出现在左子树中或右子树中,哪边出现就返回哪边,相当于下图这种情况,返回的是X,另1侧的返回null被扔掉.

    return left != null ? left : right;

    中间一段if,则表示是情况2:

            if (left != null && right != null) {
                return root;
            }
    

    在遍历的过程中,如果X与Y出现在某1个节点的2侧,最后左、右子树的遍历中, 会把它俩都返回过来,出现这种情况,说明X与Y汇聚于当前节点,这个节点就是最近的公共祖先。

    作者:菩提树下的杨过
    出处:http://yjmyzz.cnblogs.com
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    python模块
    python函数进阶
    python函数
    六.python的深浅拷贝
    五.集合及其推导式
    IOS微信显示NAN,中国标准时间日期转换
    Caused by: org.apache.ibatis.binding.BindingException: Parameter 'XXX' not found. Available parameters are [beginTime, endTime, param3, param1, vesselName, param2] 找不到参数的解决方式
    java的nullpointerexception空指针异常,关于使用了timer定时导致service调用问题
    java设置开始时间,定时执行任务
    java多层嵌套集合的json,如何获取多层集合中所需要的value
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/15490975.html
Copyright © 2020-2023  润新知