最低公共祖先问题(LCA) 举一反三

算法总是美的,充满着magic.

一.前言

给定一棵树,同时给出树中的两个结点或者两个以上节点,求它们的最低公共祖先。这就是常见的LCA(Lowest Common Ancestor )问题。

二.两个节点的LCA问题

常规解法

1.1 思路
下面是一个简单的复杂度为 O(n) 的算法,解决LCA问题
1) 找到从根到n1的路径,并存储在一个向量或数组中。
2)找到从根到n2的路径,并存储在一个向量或数组中。
3) 遍历这两条路径,直到遇到一个不同的节点,则前面的那个即为最低公共祖先.

1.2 代码

//遍历两次,时间复杂度为O(n)
    private static int findLCA(BinaryTree root, int key1, int key2) {
        if (root == null) {
            return -1;
        }
        List<Integer> path1 = new ArrayList<>();
        List<Integer> path2 = new ArrayList<>();
        boolean found1 = findPath(root,path1,key1);
        boolean found2 = findPath(root,path2,key2);
        int ans = 0;
        if (found1 && found2) {
            for (int i = 0; i < path1.size(); i++) {
                if (path1.get(i) != path2.get(i)) {
                    break;
                }else {
                    ans = path1.get(i);
                }
            }
            return ans;
        }

        return -1;
    }

    /** * @category 从根节点找到一条到指点数的路径. * @param root 二叉树根节点 * @param path 存储根节点到key的路径 * @param key 要查找的数 * @return 若路径存在则返回true,否则,false. * */
    private static boolean findPath(BinaryTree root, List<Integer> path, int key) {
        if (root == null) {
            return false;
        }
        path.add(root.value);
        if (root.value == key) {
            return true;
        }
        boolean find = (findPath(root.left, path, key)||findPath(root.right, path, key));
        if (find) {
            return true;
        }

        path.remove(path.indexOf(root.value));
        return false;
    }

优化解法

只遍历一次

1.1 思路

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

1.2 代码

/** * 从root节点向下遍历,若root.value等于任一要查找的值,则root为祖先节点, * 如果都不匹配,则遍历左子树和右子树,如果一个key出现在左子树,一个出现在右子树,则root为祖先节点, * 如果两个都出现在左子树,则说明LCA在左子树,否则在右子树. * @param root 二叉树根节点 * @param key1,key2 要查找的数 * @return 公共祖先节点 */
    private static BinaryTree findLCA(BinaryTree root, int key1, int key2) {
        if (root == null) {
            return null;
        }

        if (root.value == key1 || root.value == key2) {
            return root;
        }

        BinaryTree left = findLCA(root.left, key1, key2);
        BinaryTree right = findLCA(root.right, key1, key2);
        if (left != null && right != null) {
            return root;
        }

        return (left !=null)?left:right;
    }

三.三个节点的LCA问题

题目如题.

1.1 思路

思路同上述常规解法. 相当找三个链表的最后一个公共节点.

1.2 代码

/** * @param root 二叉树根节点 * @param key1,key2,key3 要查找的数 * @return 公共祖先节点 */
    private static int findLCA(BinaryTree root, int key1, int key2, int key3) {
        if (root == null) {
            return -1;
        }
        List<Integer> path1 = new ArrayList<>();
        List<Integer> path2 = new ArrayList<>();
        List<Integer> path3 = new ArrayList<>();
        boolean found1 = findPath(root,path1,key1);
        boolean found2 = findPath(root,path2,key2);
        boolean found3 = findPath(root,path3,key3);
        int ans = 0;
        if (found1 && found2 && found3) {
            int index = 0;
            while(index<path1.size()){
                if (path1.get(index)==path2.get(index)&&path1.get(index)==path3.get(index)) {
                    ans = path1.get(index);
                    index++;
                }else {
                    break;
                }
            }
            return ans;
        }

        return -1;
    }

    private static boolean findPath(BinaryTree root, List<Integer> path, int key) {
        if (root == null) {
            return false;
        }
        path.add(root.value);
        if (root.value == key) {
            return true;
        }
        boolean found = (findPath(root.left, path, key)||findPath(root.right, path, key));
        if (found) {
            return true;
        }
        path.remove(path.indexOf(root.value));

        return false;
    }

四.三个节点的LCA问题

给定一满二叉排序树,节点从1-(2^k-1),给出三个数a,b,c;找出它们的最低公共祖先. (腾讯题)

直接上代码:

import java.util.Scanner;

/** * LCA 问题: 满二叉排序树中三个节点的最低公共祖先 * @author dingding * @version 9-13 */
public class BT_NoParentPtr_Solution4 {

    public static void main(String[] args) {
        Scanner cin = new Scanner(System.in);
        while(cin.hasNext()){
            int k = cin.nextInt();
            int a = cin.nextInt();
            int b = cin.nextInt();
            int c = cin.nextInt();

            int result = findLCA(a,b,c,1,(2<<k)-1);
            System.out.println(result);
        }

    }

    /** * 若根节点的值为m,若存在(a-m)(b-m)<0 || (a-m)(c-m)<0 || (b-m)(c-m)<0,则节点m 为最低祖先. * @param a,b,c 查找的三个数 * @param left 数组最左边的值,right 数组最右边. (数组为二叉排序数组) * @return 公共祖先节点 */
    private static int findLCA(int a, int b, int c, int left, int right) {
        int m = left+(right-left)/2;
        if (((a-m)*(b-m)<=0) || ((a-m)*(c-m)<=0) || ((b-m)*(c-m)<=0)) {
            return m;
        }else if(a>m){  //不在根节点,a>m必有一节点在右子树,搜索右侧.
            return findLCA(a, b, c, m+1, right);
        }else {
            return findLCA(a, b, c, left, m-1);
        }

    }
}

参考文献

  1. 寻找二叉树两个节点的最低公共祖先
点赞