题目描述

✅ 1650. 二叉树的最近公共祖先 III

题意分析

两个目标节点属于同一棵树,节点带有 parent 指针,接口没有给出根节点。沿父指针可以直接访问所有祖先,目标节点自身也应包含在候选中。

解法:父链双指针换头

核心思路

[!blue]
从 p、q 分别沿 parent 走到根,就得到两条链。一旦经过同一个节点,后续父节点也完全相同,因此两条链共享一个后缀。这个公共后缀的第一个节点,就是从下向上遇到的最近公共祖先。

两个指针分别从 p、q 出发,每轮都前进一步。非空时移到父节点,走到空后则切换到对方的起点。这样较短父链的指针会先换头,之后双方都补走另一条链的独有部分,消除起始深度差。

设两条链在公共后缀之前分别有 $a$、$b$ 个节点,公共后缀有 $c$ 个节点。若此前还没有相遇,换头后到达公共后缀起点,两个指针分别走了 $a+c+1+b$ 和 $b+c+1+a$ 步,其中 $1$ 是从空指针切换到另一链起点的一步。总步数相同,因此会同时到达最近公共祖先。

比较的是节点身份,首次相等就返回。若两个目标相同,循环一开始就结束;若其中一个是另一个的祖先,起点也保留在父链中,不会漏掉它。

解题步骤

  1. 初始化两个指针为 p、q。
  2. 指针不同就同步推进。
  3. 非空指针走向父节点,空指针切换到对方起点。
  4. 相遇时返回该节点。

代码实现

class Solution {
    public Node lowestCommonAncestor(Node p, Node q) {
        Node a = p;
        Node b = q;

        while (a != b) {
            // 走空后切换到对方起点,抵消两条父链的长度差。
            a = a == null ? q : a.parent;
            b = b == null ? p : b.parent;
        }

        return a;
    }
}
func lowestCommonAncestor(p *Node, q *Node) *Node {
    a, b := p, q
    for a != b {
        // 走空后切换到对方起点,抵消两条父链的长度差。
        if a == nil {
            a = q
        } else {
            a = a.Parent
        }
        if b == nil {
            b = p
        } else {
            b = b.Parent
        }
    }
    return a
}

复杂度分析

  • 时间复杂度:$O(h_p+h_q+1)$,$h_p$、$h_q$ 为两点到根的路径长度,每个指针至多走过两条父链。
  • 空间复杂度:$O(1)$,只使用两个指针。

关键点总结

[!green]

  • 最近公共祖先对应父链的第一个交点。
  • 换头抵消深度差,无需单独计算深度。
  • 节点自身也可能是祖先,起点必须包含自身。

易错点总结

[!yellow]

  • 从 parent 开始:可能跳过正确答案。
  • 走空后回到自己的起点:没有补齐另一条链的长度差。
  • 两指针推进次数不一致:破坏同步行走的前提。

相似题目

题目 难度 关联与区别
160. 相交链表 简单 沿parent向上就是两条最终相交的链,链表相交的双指针切换写法可直接用于寻找共同祖先。
236. 二叉树的最近公共祖先 中等 普通LCA从根往下查,本题已有父指针,可以直接比较两条祖先链。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2024/96117663
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!