剑指 Offer 68 - II. 二叉树的最近公共祖先

题目介绍

描述:

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

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

例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]

示例 1:

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

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

解题思路:

递归算法的关键是要明确函数的「定义」是什么,然后相信这个定义,利用这个定义推导最终结果。

写树相关的算法,简单说就是,先搞清楚当前 root 节点该做什么,然后根据函数定义递归调用子节点,递归调用会让孩子节点做相同的事情。

二叉树题目的一个难点在于如何通过题目的要求思考出每一个节点需要做什么

二叉树解题策略

一 递归 二 队列 + 迭代 (层次遍历) 三 栈 + 迭代 (非递归遍历) 四 其它

三种基本的遍历方式,都可以用递归来实现。写递归算法的时候,需要注意递归退出条件以及递归操作的表达。

本题和之前某题不一样的地方是,本题是二叉树,不是搜索二叉树。

自己的解法实现

def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
        if not root or root == p or root == q: return root
        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)
        if left and right: return root
        return left or right

网上比较优秀的解法

解法一

解题思路: 祖先的定义: 若节点 p 在节点 root 的左(右)子树中,或 p=root ,则称 root 是 p 的祖先。

最近公共祖先的定义: 设节点 root 为节点 p,q 的某公共祖先,若其左子节点 root.left 和右子节点 root.right 都不是 p,q 的公共祖先,则称 root 是 “最近的公共祖先” 。

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

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

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

def lowestCommonAncestor(self, root, p, q):
        if not root or root == p or root == q: return root
        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)
        if not left: return right
        if not right: return left
        return root

解法二

由于lowestCommonAncestor(root, p, q)的功能是找出以root为根节点的两个节点p和q的最近公共祖先。 我们考虑:

  1. 如果p和q分别是root的左右节点,那么root就是我们要找的最近公共祖先
  2. 如果root是None,说明我们在这条寻址线路没有找到,我们返回None表示没找到 我们继续在左右子树执行相同的逻辑。
  3. 如果左子树没找到,说明在右子树,我们返回lowestCommonAncestor(root.right, p , q)
  4. 如果右子树没找到,说明在左子树,我们返回lowestCommonAncestor(root.left, p , q)
  5. 如果左子树和右子树分别找到一个,我们返回root
def lowestCommonAncestor2(self, root, p, q):
        if not root or root == p or root == q: return root
        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)
        return root if left and right else left or right

解法三

1.将每个节点标记,记录在字典(哈希表)里,方式为:从根节点开始,向左为0,向右为1,如示例中节点7标记为“010”,节点4标记为“011” 2.由两个节点的标记从头开始的公共部分得到祖先的标记,如7和4的祖先标记为“01” 3.由祖先的标记得到祖先节点2

def lowestCommonAncestor3(self, root, p, q):
        stack = [root]
        _dic = {root: ""}
        while stack:
            tmp = []
            for e in stack:
                if e.left:
                    _dic[e.left] = _dic[e] + "0"
                    tmp.append(e.left)
                if e.right:
                    _dic[e.right] = _dic[e] + "1"
                    tmp.append(e.right)
            stack = tmp
        # 标记完成,接下来根据标记找到pq的最近公共祖先的标记
        p_tag = _dic[p]
        q_tag = _dic[q]
        num = 0
        res_tag = ""
        while num < len(p_tag) and num < len(q_tag):
            if p_tag[num] != q_tag[num]:
                continue
            else:
                res_tag += p_tag[num]
                num += 1
        for k, v in _dic.items():
            if v == res_tag:
                return k

相关知识总结和思考

相关知识:

BFS:广度/宽度优先。其实就是从上到下,先把每一层遍历完之后再遍历一下一层。

可以使用Queue的数据结构。我们将root节点初始化进队列,通过消耗尾部,插入头部的方式来完成BFS。

二叉搜索树(BST)的特性:

  1. 若它的左子树不为空,则所有左子树上的值均小于其根节点的值
  2. 若它的右子树不为空,则所有右子树上的值均大于其根节点的值
  3. 它的左右子树也分别为二叉搜索树

递归与迭代的区别

递归:重复调用函数自身实现循环称为递归; 迭代:利用变量的原值推出新值称为迭代,或者说迭代是函数内某段代码实现循环;

你可能感兴趣的:(剑指 Offer 68 - II. 二叉树的最近公共祖先)