查找二叉树的最近公共祖先-倍增实现

125 阅读2分钟

最近公共祖先 LCA

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

倍增法

倍增法:binary lifting。

它能够使线性的处理转化为对数级的处理,大大地优化时间复杂度。常用于RMQ 问题和LCA问题。

“倍增”与“二进制划分”两个思想相互结合,降低了求解很多问题的时间与空间复杂度。

基本用法如下:倍增主要用途是为了查找单调数据组中某一数值

比如:在一个数组a {2,5,7,11,19} 中查找最大的小于12的数字。

(1)朴素做法:从第一个数开始,一个一个往后枚举,查找。

(2)二分做法:每次将数列分割一半判断,并且进一步查找子区间。

(3)倍增做法:设定一个增长长度 p 和已确定长度 l,现在要确定 a[l+p] 是否满足条件,若满足条件(比12小),则p成2倍增长;否则 p 缩小范围(试着缩小范围判断条件)。

倍增算法一般比较稳定,时间 O(logn)。

问题解决思路

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

解题思路如下:

  1. 查找两个指定节点在二叉树中的father路径
  2. 对两个路径的公共长度,采用倍增法查找

节点代码如下:

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

    TreeNode(int x) {
        val = x;
    }
}

查找节点的father路径

List<TreeNode> getNodeFatherPath(TreeNode curNode, TreeNode target, List<TreeNode> path) {
    path.add(curNode);

    // 找到目标节点,返回
    if (curNode.val == target.val) {
        return path;
    }

    // 左节点存在
    if (Objects.nonNull(curNode.left)) {
        List<TreeNode> result = getNodeFatherPath(curNode.left, target, path);
        if (!result.isEmpty()) {
            return result;
        }
    }

    if (Objects.nonNull(curNode.right)) {
        List<TreeNode> result = getNodeFatherPath(curNode.right, target, path);
        if (!result.isEmpty()) {
            return result;
        }
    }

    path.remove(curNode);
    return new ArrayList<>();
}

倍增查找LCA

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    // 查找节点路径
    List<TreeNode> pPath = getNodeFatherPath(root, p, new ArrayList<>());
    List<TreeNode> qPath = getNodeFatherPath(root, q, new ArrayList<>());

    int len = pPath.size(); // 两个节点路径的公共长度
    if (len > qPath.size()) {
        len = qPath.size();
    }

    // 如果初始两个节点在一个路径,直接返回
    if (pPath.get(len - 1).val == qPath.get(len - 1).val) {
        return pPath.get(len - 1);
    }


    while (true) {
        int number = getNumber(len); // 2的number次方小于len

        while (true) {
            int skipStep = (int) Math.pow(2, number); // 需要跳的层级数
            int index = len - skipStep - 1; // 跳之后的节点层级

            if (pPath.get(index).val == qPath.get(index).val) { // 节点值相同,表示重合,继续跳
                number--;
            } else if (pPath.get(index - 1).val == qPath.get(index - 1).val) { // 节点值不同,但是父节点相同,表示查找成功
                return pPath.get(index - 1);
            } else { // 缩短长度,继续跳
                len = index + 1;
                break;
            }
        }
    }
}

/**
 * 计算倍增时,从2的n次方开始 中的n
 * 也就是 最接近target的2的次幂
 * @param target
 * @return
 */

int getNumber(int target) {
    int start = 1;
    for (int i = 0; i < 31; i++) {
        start *= 2;
        if (start > target) {
            return i - 1;
        }
    }

    return 0;
}