Skip to content

M236.二叉树的最近公共祖先

dfs, binary tree, https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/

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

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

示例 1:

img

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。

示例 2:

img

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。

示例 3:

输入:root = [1,2], p = 1, q = 2
输出:1

提示:

  • 树中节点数目在范围 [2, 10^5] 内。
  • -10^9 <= Node.val <= 10^9
  • 所有 Node.val 互不相同
  • p != q
  • pq 均存在于给定的二叉树中。

这个问题可以通过后序遍历(递归)的思想来解决。

解题思路

在递归遍历时,我们从底向上寻找 pq。对于当前节点 root

  1. 边界情况

    • 如果 root 为空,直接返回 None
    • 如果 root 就是 p 或者 q,那么 root 本身就是我们要找的(或者是其祖先),直接返回 root
  2. 递归寻找

    • 去左子树寻找 pq,结果记为 left
    • 去右子树寻找 pq,结果记为 right
  3. 判断结果

    • 情况 1(左右开花):如果 leftright 都不为空,说明 pq 分别分布在当前节点的左右子树中,那么当前节点 root 就是最近公共祖先。
    • 情况 2(全在左边):如果 left 不为空而 right 为空,说明 pq 都在左子树里,返回 left
    • 情况 3(全在右边):如果 right 不为空而 left 为空,说明 pq 都在右子树里,返回 right
    • 情况 4(都没找到):如果都为空,说明这棵树里没有 pq,返回 None

    代码实现

python
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        # 1. 如果 root 为空,或者 root 就是我们要找的节点之一,直接返回 root
        if not root or root == p or root == q:
            return root
        
        # 2. 递归在左子树和右子树中查找
        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)
        
        # 3. 如果左子树找到了一个,右子树也找到了一个
        # 说明 p 和 q 分居 root 的两侧,root 就是 LCA
        if left and right:
            return root
        
        # 4. 如果只有左子树找到了,返回左子树的结果
        if left:
            return left
        
        # 5. 如果只有右子树找到了,返回右子树的结果
        if right:
            return right
        
        # 6. 如果都没找到,返回 None
        return None

复杂度分析

  • 时间复杂度O(N)。其中 N 是二叉树的节点数。在最坏情况下,我们需要遍历二叉树的所有节点。
  • 空间复杂度O(N)。这是由递归调用的栈深度决定的。最坏情况下(树退化成链表),递归深度为 N;平衡树情况下为 O(logN)

为什么这个逻辑是正确的?

由于我们是后序遍历(先处理左右子树,再处理根节点),所以当我们第一次遇到 leftright 同时不为空的情况时,当前的 root 必然是深度最大的那个祖先(即最近公共祖先)。之后这个 root 会作为返回值不断向上传递,最终成为整棵树的查询结果。