问题提出

  • 如何计算树上任意两点 xy 的最近公共祖先 呢?
  • 通俗地理解-假设在一棵二叉树中,有两个节点 最近公共祖先-算法学习_取整最近公共祖先-算法学习_List_02
  • 那么该如何求这两个节点的最近公共祖先节点最近公共祖先-算法学习_List_03
  • 如下图,节点 最近公共祖先-算法学习_最近公共祖先_04和节点最近公共祖先-算法学习_最近公共祖先_05的最近公共祖先节点是最近公共祖先-算法学习_List_03

最近公共祖先-算法学习_List_07

思路解析

  1. 假设一个节点的深度为,这可以通过一次 DFS 预处理出来。
  1. 那么这里如何进行预处理呢?
  2. 单纯找一个节点最近公共祖先-算法学习_取整_08的深度的解决办法较为简单
  1. 假设最近公共祖先-算法学习_取整_09(否则交换两个节点的位置)
  2. 可以先把更靠下的更新为第个祖先节点,这样和就处于同一深度了
  1. 这一句话还不理解,然后怎么理解呢?
  2. 如图 图-Tree1 节点最近公共祖先-算法学习_取整_10的深度是最近公共祖先-算法学习_最近公共祖先_05,节点最近公共祖先-算法学习_最近公共祖先_12的深度是最近公共祖先-算法学习_List_13
  3. 那么就把更靠下的最近公共祖先-算法学习_最近公共祖先_12更新为其第最近公共祖先-算法学习_最近公共祖先_15个祖先节点节点,也就是节点最近公共祖先-算法学习_最近公共祖先_16
  1. 如果此时,那么就是
  1. 这里意思是如果节点最近公共祖先-算法学习_List_02更新之后等于节点最近公共祖先-算法学习_取整那么这两个节点的公共节点就是最近公共祖先-算法学习_取整
  2. 否则说明最近公共祖先-算法学习_List_03在更上面,那么就把最近公共祖先-算法学习_取整最近公共祖先-算法学习_List_02一起往上跳
  1. 由于不知道最近公共祖先-算法学习_List_03的具体位置,只能不断尝试,先尝试大步跳,再尝试小步跳。
  2. 最近公共祖先-算法学习_List_24(表示对 以 2 为底 n 的对数进行向下取整),循环直到 最近公共祖先-算法学习_取整_25
  3. 每次循环逻辑如下
  1. 如果最近公共祖先-算法学习_取整的第最近公共祖先-算法学习_取整_27个祖先节点不存在,即最近公共祖先-算法学习_取整_28,说明步子迈大了,将最近公共祖先-算法学习_取整_08减 1,继续循环
  2. 如果的第个祖先节点存在,且。说明在的上面
  1. 那么更新最近公共祖先-算法学习_List_30,将最近公共祖先-算法学习_取整_08最近公共祖先-算法学习_最近公共祖先_04然后继续循环
  1. 最近公共祖先-算法学习_取整_33,那么最近公共祖先-算法学习_List_03可能在最近公共祖先-算法学习_最近公共祖先_35的下面,由于无法向下跳,只能将最近公共祖先-算法学习_取整_08最近公共祖先-算法学习_最近公共祖先_04,然后继续循环
  1. 循环结束的时候,与只有一步之遥,即
  1. 是如何得出该结论的?

  • 以寻找节点最近公共祖先-算法学习_最近公共祖先_38和节点最近公共祖先-算法学习_取整_39的最近公共祖先节点为例

最近公共祖先-算法学习_List_40

  • 那么首先更新更深的节点最近公共祖先-算法学习_最近公共祖先_38最近公共祖先-算法学习_取整_42从而保证了和节点最近公共祖先-算法学习_取整_39位于相同的深度

最近公共祖先-算法学习_取整_44

然后开始向上寻找,这里假设树或者图一共有最近公共祖先-算法学习_List_45个节点,那么极端情况下,最底层的节点的第最近公共祖先-算法学习_取整_46个祖先节点是根节点

又因为一个正整数可以写成若干个 2 次幂数的和,即寻找一个节点的祖先节点的时候,可以依次寻找其第最近公共祖先-算法学习_取整_27个祖先节点

ex:最近公共祖先-算法学习_取整_48,即寻找一个节点的第13个祖先节点,可以依次寻找第最近公共祖先-算法学习_最近公共祖先_49个祖先节点即可。

  • 所以这里有 最近公共祖先-算法学习_List_24,向下取整是因为节点本身需要被忽略
  • 以图示为例,这里可以计算得出最近公共祖先-算法学习_取整_51
  • 这里设
  • 那么很显然最开始最近公共祖先-算法学习_取整的第最近公共祖先-算法学习_最近公共祖先_53个祖先节点不存在
  • 最近公共祖先-算法学习_List_54,第最近公共祖先-算法学习_最近公共祖先_55个祖先节点也不存在,继续 最近公共祖先-算法学习_List_56
  • 直到最近公共祖先-算法学习_最近公共祖先_57,则最近公共祖先-算法学习_取整的第最近公共祖先-算法学习_List_59个祖先节点是节点最近公共祖先-算法学习_最近公共祖先_05
  • 那么此时对于最近公共祖先-算法学习_最近公共祖先_61而言,其第最近公共祖先-算法学习_List_13个祖先节点相同。但是暂时不确定是不是最近的公共祖先节点
  • 所以 继续 最近公共祖先-算法学习_取整_63,此时需要找到节点最近公共祖先-算法学习_最近公共祖先_61的第最近公共祖先-算法学习_List_65个公共节点分别是最近公共祖先-算法学习_取整_66
  • 此时满足最近公共祖先-算法学习_取整_67,则更新最近公共祖先-算法学习_取整_68,然后更新最近公共祖先-算法学习_最近公共祖先_69
  • 此时寻找最近公共祖先-算法学习_最近公共祖先_61的第最近公共祖先-算法学习_List_71个祖先节点分别是最近公共祖先-算法学习_取整_72,然后更新最近公共祖先-算法学习_最近公共祖先_73
  • 此时退出循环。则最近公共祖先节点--

思路总结

  1. 总的来说,寻找祖先节点,需要使用二维数组来缓存节点的祖先节点。
  2. 然后利用最近公共祖先-算法学习_取整_74方法来寻找公共组建节点,降低查找次数

代码实现

  • 通常入参是 二维最近公共祖先-算法学习_取整_75数组的形式,然后利用最近公共祖先-算法学习_取整_75来建图
class TreeAncestor {

    // 缓存公共祖先节点
    private int[][] cache;
    private int[] depth;

    public TreeAncestor(int[][] edges) {
        int n = edges.length - 1;
        int m = 32 - Integer.numberOfLeadingZeros(n);
        // 使用 List 数组表示 邻接表
        List<Integer> g[] = new ArrayList[];
        // 初始化数组元素
        Arrays.setAll(g, e -> new ArrayList<>());
        // 遍历节点
        for(int[] edge : edges) {
            int x = e[0], y = e[1];
            g[x].add(y);
            g[y].add(x);
        }
        depth = new int[n];
        cache = new int[n][m];
        
        dfs(g, 0, -1);

        for (int i = 0; i < m - 1; i++) {
            for (int x = 0; x < n; x++) {
                int p = cache[x][i];
                cache[x][i + 1] = p < 0 ? -1 : pa[p][i];
            }
        }
    }

    private void dfs(List<Integer>[] g, int x, int fa) {
        cache[x][0] = fa;
        for (int y : g[x]) {
            if (y != fa) {
                depth[y] = depth[x] + 1;
                dfs(g, y, x);
            }
        }
    }

    public int getKthAncestor(int node, int k) {
        for (; k > 0; k &= k - 1)
            node = cache[node][Integer.numberOfTrailingZeros(k)];
        return node;
    }

    public int getLCA(int x, int y) {
        if (depth[x] > depth[y]) {
            int tmp = y;
            y = x;
            x = tmp;
        }
        // 使 y 和 x 在同一深度
        y = getKthAncestor(y, depth[y] - depth[x]);
        if (y == x)
            return x;
        for (int i = cache[x].length - 1; i >= 0; i--) {
            int px = cache[x][i], py = pa[y][i];
            if (px != py) {
                x = px;
                y = py;
            }
        }
        return cache[x][0];
    }

    
}

参考

https://leetcode.cn/problems/kth-ancestor-of-a-tree-node/solution/mo-ban-jiang-jie-shu-shang-bei-zeng-suan-v3rw/