Leetcode Link: 236. 二叉树的最近公共祖先 - 力扣(LeetCode) (leetcode-cn.com) 剑指 Offer 68 - II. 二叉树的最近公共祖先 - 力扣(LeetCode)

题目

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

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

解法一

思路:DFS,自己思路,代码写出来有点low 明确一点,该代码需要得知左右子树里各自含有目标节点的个数,然后判断该节点是不是目标节点,三者一起考虑来确定该节点是不是公共的祖先节点

因为如果该节点为目标节点,而另一个目标节点是该节点的子孙节点,那么该节点就是公共祖先节点,因此在判断的时候需要先知道该节点是不是目标节点。

题解

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        self.p = p
        self.q = q
        self.common_ancestor = None
        # 这个flag用来指示已经找到了祖先节点
        # 如果没有这个,那么假设p,q都在一个子树上,那么这个子树的父节点,爷爷节点会一直覆盖正确的值
        self.find_flag = False
        self.recur(root)
        return self.common_ancestor
 
    def recur(self, node):
        if not node:
            return 0
 
        num_left = self.recur(node.left)
        num_right = self.recur(node.right)
        # 判断当前的节点是不是目标节点
        if node == self.p or node == self.q:
            nums = 1
        else:
            nums = 0
        # 判断这个节点是不是公共的祖先节点
        if (nums + num_left + num_right == 2) and (not self.find_flag):
            self.find_flag = True
            self.common_ancestor = node
        # 返回当前子树中含有的目标节点的数量
        return nums + num_left + num_right

解法二

思路: 根据以上定义,若 root 是 p, q 的 最近公共祖先 ,则只可能为以下情况之一:

p 和 q 在 root 的子树中,且分列 root 的 异侧(即分别在左、右子树中); p = root ,且 q 在 root 的左或右子树中; q = root ,且 p 在root 的左或右子树中;

考虑通过递归对二叉树进行先序遍历,当遇到节点 p 或 q 时返回。从底至顶回溯,当节点 p, q在节点 root 的异侧时,节点 root 即为最近公共祖先,则向上返回 root 。

递归解析:

  • 终止条件: 当越过叶节点,则直接返回 null ; 当 root 等于 p,q ,则直接返回 root ;
  • 递推工作: 开启递归左子节点,返回值记为 left ; 开启递归右子节点,返回值记为 right ; 返回值: 根据 left 和 right ,可展开为四种情况;
  1. 当 left 和 right 同时为空 :说明 root 的左 / 右子树中都不包含 p,q ,返回 null ;
  2. 当 left 和 right 同时不为空 :说明 p,q 分列在 root 的 异侧 (分别在 左 / 右子树),因此 root 为最近公共祖先,返回 root ;
  3. 当 left 为空 ,right 不为空 :p,q 都不在 root 的左子树中,直接返回 right 。具体可分为两种情况:
    1. p,q 其中一个在 root 的 右子树 中,此时 right 指向 p(假设为 p );
    2. p,q 两节点都在 root 的 右子树 中,此时的 right 指向 最近公共祖先节点 ;
  4. 当 left 不为空 , right 为空 :与情况 3. 同理;

本思路通过返回值是不是None就直接相当于我的思路的 find_flag 的作用。如果在同一个子树上,那就就会一直返回那个子树的头节点,也就是q, p的公共祖先节点。返回的过程中,另一边的子树返回的是None,就不会覆盖。

题解

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if not root:
            return None
        
        # 碰到了就返回就行
        if root == q or root == p:
            return root
        
        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)
        #1 左右都不为空,说明该节点为根节点
        if (left!=None and right!=None): return root
        #2 左右有一个为空,说明有一个在该子树上,另一个不在
        if (left==None and right!=None): return right
        if (left!=None and right==None): return left
        #3 左右都为空,说明都不在子树上
        if (left==None and right==None): return None

启发和联系