二叉树系列4:lowest common ancestor

【要求】:找出二叉树上任意两个节点的最近共同父结点。

这道题的简化版本就是将“二叉树”换成“搜索二叉树”,那么可以通过节点的值的比较利用递归来判断。这里,由于是指任意二叉树,任意节点,所以,只能通过搜索来查找。LCA问题的解法非常多,这里介绍的只是一部分。


1 通过各自的路径求解

由于题目是求得两个节点的最低的公共祖先,所以可以先求得从根到两个目标节点的路径,然后比较路径求解。我这里直接贴源代码,如果想了解求路径的详细知识,可以参考博主的上一篇博文:【二叉树系列3: 二叉树的路径问题】

TreeNode.java

package BinaryTree;

public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    public TreeNode(TreeNode l, TreeNode r, int v) {
        left = l;
        right = r;
        val = v;
    }

    public TreeNode(int v) {
        val = v;
    }

}

BinaryTreeTest.java

核心:

    public TreeNode lowestCommonAncestor_byPath(TreeNode t, int a, int b) {
        if(t == null){
            System.err.println("The tree is null!");
            return null;
        }

        if(a == b){
            System.err.println("Two nodes are the same!");
            return null;
        }

        ArrayList<TreeNode> aPath, bPath;
        aPath = printPath(t, a);
        bPath = printPath(t, b);
        if(aPath == null || aPath == null){
            System.err.println("Cannot find the node!");
            return null;
        }

        TreeNode aNode, bNode, common;
        common = t;
        int len = Math.min(aPath.size(), bPath.size());
        for(int i=0; i< len; i++){
            aNode = aPath.get(i);
            bNode = bPath.get(i);
            if(aNode.val == bNode.val){
                common = aNode;
            }
            else {
                return common;
            }
        }

        return t;
    }

求路径的代码:

    /** 包装方法 */
    public ArrayList<TreeNode> printPath(TreeNode t, int val) {
        ArrayList<TreeNode> path = new ArrayList<>();
        return printPath(t, val, path);
    }

    /** 输出给定节点到根节点的路径 */
    private ArrayList<TreeNode> printPath(TreeNode t, int val, ArrayList<TreeNode> path) {
        if (t == null) {
            return null;
        }

        path.add(t);
        // 如果找到就直接返回
        if (val == t.val) {
            //System.out.println("Find!");
            return path;
        } else {
            // 如果没有找到则搜索它的左子树
            ArrayList<TreeNode> leftPath = (ArrayList<TreeNode>) path.clone();
            ArrayList<TreeNode> lRet = printPath(t.left, val, leftPath);

            if (lRet != null) {
                // 说明在做子树中找到了这个点,则返回
                path = null;
                return lRet;
            }
            // 如果在左子树没有找到,则搜索右子树
            else {
                ArrayList<TreeNode> rightPath = (ArrayList<TreeNode>) path.clone();
                ArrayList<TreeNode> rRet = printPath(t.right, val, rightPath);
                if (rRet != null) {
                    path = null;
                    return rRet;
                }
                // 说明右子树也没有找到
                else {
                    path = null;
                    return null;
                }
            }
        }
    }

【测试】Main.java

package BinaryTree;

public class Main {

    public static void main(String[] args) {
        /***
         * 新建一个二叉树:
         * 
         *            15
         *           /  \
         *          12   25
         *         / \
         *        9   13
         * ***/
        TreeNode root = new TreeNode(15);
        TreeNode l = new TreeNode(12);
        root.left = l;
        TreeNode r = new TreeNode(25);
        root.right = r;
        TreeNode ll = new TreeNode(9);
        TreeNode lr = new TreeNode(13);
        l.left = ll;
        l.right = lr;

        BinaryTreeTest bTreeTest = new BinaryTreeTest();
        TreeNode a = bTreeTest.lowestCommonAncestor_byPath(root, 9, 13);
        System.out.printf("\nThe lowest common ancestor of 9 and 13 is: %-4d ", a.val);

        TreeNode b = bTreeTest.lowestCommonAncestor_byPath(root, 9, 25);
        System.out.printf("\nThe lowest common ancestor of 9 and 25 is: %-4d ", b.val);
    }

}

结果:

The lowest common ancestor of 9 and 13 is: 12   
The lowest common ancestor of 9 and 25 is: 15   

此算法的主要开销是获取根节点到指定节点的路径,总共两次,每次就是对利用先序遍历对树进行搜索,所以时间复杂度是 O(N) ,空间复杂度是O (log N)。



2 一次遍历求解

上面的方法虽然是O(N),但是操作依然繁琐了一点,并且需要额外的空间来存储路径。上面的代码很多有木有!? 我敲得都烦了。其实可以只遍历一次,利用递归的巧妙之处。学好二叉树,其实就是学好递归。

从root开始遍历,如果n1和n2中的任一个和root匹配,那么root就是LCA。 如果都不匹配,则分别递归左、右子树,如果有一个 key(n1或n2)出现在左子树,并且另一个key(n1或n2)出现在右子树,则root就是LCA. 如果两个key都出现在左子树,则说明LCA在左子树中,否则在右子树。

    public TreeNode findLCA(TreeNode t, int a, int b) {
        if (t == null) {
            return null;
        }

        if (t.val == a || t.val == b) {
            return t;
        }

        TreeNode leftResult = findLCA(t.left, a, b);
        TreeNode rightResult = findLCA(t.right, a, b);
        // 如果在左子树找到一个值,在右子树也找到一个值,毫无疑问,LCA就是root
        if (leftResult != null && rightResult != null) {
            return t;
        }
        // 如果左子树找到了一个值,但是右子树一个都没有找到,这说明两个节点都在左子树中
        // 由于我们采用的是中序遍历,每次进行判断的都是目前的root,所以左子树的返回值就是LAC
        else if (leftResult != null && rightResult == null) {
            return leftResult;
        }
        // 两个节点全在右子树中
        else if (leftResult == null && rightResult != null) {
            return rightResult;
        }
        // 如果左右子树都没有搜索到,则节点不在树种
        else {
            return null;
        }
    }

测试:

package BinaryTree;

public class Main {

    public static void main(String[] args) {
        /***
         * 新建一个二叉树:
         * 
         *            15
         *           /  \
         *          12   25
         *         / \
         *        9   13
         * ***/
        TreeNode root = new TreeNode(15);
        TreeNode l = new TreeNode(12);
        root.left = l;
        TreeNode r = new TreeNode(25);
        root.right = r;
        TreeNode ll = new TreeNode(9);
        TreeNode lr = new TreeNode(13);
        l.left = ll;
        l.right = lr;

        BinaryTreeTest bTreeTest = new BinaryTreeTest();
        TreeNode a = bTreeTest.findLCA(root, 9, 13);
        System.out.printf("\nThe lowest common ancestor of 9 and 13 is: %-4d ", a.val);

        TreeNode b = bTreeTest.findLCA(root, 9, 25);
        System.out.printf("\nThe lowest common ancestor of 9 and 25 is: %-4d ", b.val);

        TreeNode c = bTreeTest.findLCA(root, 9, 33);
        try {
            System.out.printf("\nThe lowest common ancestor of 9 and 33 is: %-4d ", c.val);
        } catch (Exception e) {
            System.out.println("\nThe lowest common ancestor of 9 and 33 is:" + e.toString());
        }

        TreeNode d = bTreeTest.findLCA(root, 0, 44);
        try {
            System.out.printf("\nThe lowest common ancestor of 0 and 44 is: %-4d ", d.val);
        } catch (Exception e) {
            System.out.println("\nThe lowest common ancestor of 0 and 44 is:" + e.toString());
        }
    }

}

结果:

The lowest common ancestor of 9 and 13 is: 12   
The lowest common ancestor of 9 and 25 is: 15   
The lowest common ancestor of 9 and 33 is: 9    
The lowest common ancestor of 0 and 44 is:java.lang.NullPointerException 

结果是有问题的。虽然此算法可以反映出两个节点都不在树中的议程情况,但是没有考虑【一个节点在树中,一个不在】的异常。造成这样的错误的原因是,在一个子树的搜索结果为空,另一个不为空的情况时,我们其实只确认了一个节点的存在性,另一个没有去验证。所以,这个算法如果在不需要考虑节点不存在的情况下很好用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值