📌  相关文章
📜  二叉树中任意数量节点的最小公共祖先(1)

📅  最后修改于: 2023-12-03 14:49:01.172000             🧑  作者: Mango

二叉树中任意数量节点的最小公共祖先

在二叉树中,给定任意数量的节点,它们的最小公共祖先(Lowest Common Ancestor,LCA)是指最深的(离根节点最远的)同时拥有这些节点的祖先节点。

如下图所示,节点 3 和节点 4 的 LCA 是节点 1,而节点 2 和节点 4 的 LCA 是节点 2。

二叉树LCA演示图

解法

解法一:递归

最直观的解法是递归。对于当前节点 root,如果其中至少有一个目标节点,则返回 root。否则,递归到它的左子树和右子树,分别记作 left 和 right。如果 left 和 right 都非空,则说明目标节点分别在左右子树上,因此 root 为 LCA;如果 left 或 right 有且仅有一个非空,说明目标节点都在该非空节点的子树中,该非空节点为 LCA;否则,两个子树中均不包含目标节点,返回空。

示例代码如下:

class Solution(object):
    def lowestCommonAncestor(self, root, nodes):
        if not root or root in nodes:
            return root
        left, right = [self.lowestCommonAncestor(child, nodes) for child in (root.left, root.right)]
        return root if left and right else left or right

时间复杂度:每个节点仅被遍历一次,因此时间复杂度为 O(n),其中 n 是节点数。

空间复杂度:每次递归调用栈的深度不会超过树的高度 H,即 O(H)。最坏情况下树呈链状,H=n,因此空间复杂度为 O(n)。

解法二:存储父节点

上述递归解法从上向下递归查找 LCA。解法二换个思路,从下向上查找 LCA。解法二的前提是,题目中给定的节点之间存在 LCA。如果题目给定的节点之间不存在 LCA,则解法二无法处理。

我们可以先遍历一遍二叉树,用哈希表记录每个节点的父节点,并将所有目标节点存放到集合中。随后,从任意一个目标节点开始,沿着父节点指针不断向上跳,直到到达根节点,并在哈希表中保存已遍历过的所有节点。随后,从另一个目标节点开始,同样不断向上跳,如果碰到在哈希表中已经出现过的节点,则该节点为 LCA。否则,继续向上跳。

示例代码如下:

class Solution(object):
    def lowestCommonAncestor(self, root, nodes):
        parent = {root: None}
        def dfs(node):
            if node.left:
                parent[node.left] = node
                dfs(node.left)
            if node.right:
                parent[node.right] = node
                dfs(node.right)
        dfs(root)

        ancestors = set()
        while nodes:
            node = nodes.pop()
            while node:
                if node in ancestors:
                    return node
                ancestors.add(node)
                node = parent[node]
        return None

时间复杂度:第一次遍历用时 O(n),随后的查找用时取决于树的高度 H,即 O(H)。最坏情况下树呈链状,H=n,因此时间复杂度为 O(n)。

空间复杂度:哈希表和集合分别存储了所有节点和目标节点,因此空间复杂度为 O(n)。

总结

本文介绍了二叉树中任意数量节点的最小公共祖先问题,给出了两种解法:递归解法和存储父节点解法。递归解法需要从上向下递归查找 LCA,时间复杂度为 O(n),空间复杂度为 O(n)。存储父节点解法则需要从下向上查找 LCA,时间复杂度为 O(n),空间复杂度为 O(n)。