二叉树/二叉搜索树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 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。因为根据定义最近公共祖先节点可以为节点本身。
说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。

解法 1

对二叉树进行中序遍历,如果 p 在当前节点的左子树且 q 在当前节点的右子树;或者当前节点为 p 或 q 的其中一个,且另一个在当前节点的左子树或右子树中,那么当前节点即为 p 和 q 的最近公共祖先。

class Solution:
    def __init__(self):
        # Variable to store LCA node.
        self.ans = None
    def lowest_common_ancestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        def recurse_tree(root):

            # If reached the end of a branch, return False.
            if not root:
                return False

            # Left Recursion
            left = recurse_tree(root.left)

            # Right Recursion
            right = recurse_tree(root.right)

            # If the current node is one of p or q
            mid = root == p or root == q

            # If any two of the three flags left, right or mid become True.
            if mid + left + right >= 2:
                self.ans = root

            # Return True if either of the three bool values is True.
            return mid or left or right

        # Traverse the tree
        recurse_tree(root)
        return self.ans

执行用时 :84 ms
内存消耗 :28.8 MB

时间复杂度:O(n),最坏的情况遍历二叉树所有 n 个节点。
空间复杂度:O(n),递归堆栈使用的最大空间位,斜二叉树的高度可以是 n。

解法 2

通过广度优先遍历所有节点,将由当前节点值和当前父节点值组成的键值对放入父指针字典中,可以利用该字典获取父节点,将 p 到 root 路径上的节点放入祖先集合,再遍历 q 到 root 路径上的节点,出现在祖先集合中的第一个相同节点即为 p 和 q 的最近公共祖先。

def lowest_common_ancestor(root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
    # Stack for tree traversal
    stack = [root]
    # Dictionary for parent pointers
    parent = {root.val: None}

    # Iterate until we find both the nodes p and q
    while p not in parent or q not in parent:
        node = stack.pop()
        # While traversing the tree, keep saving the parent pointers.
        if node.left:
            parent[node.left.val] = node.val
            stack.append(node.left)
        if node.right:
            parent[node.right.val] = node.val
            stack.append(node.right)

    # Ancestors set() for node p.
    ancestors = set()
    # Process all ancestors for node p using parent pointers.
    while p:
        ancestors.add(p)
        p = parent[p]

    # The first ancestor of q which appears in
    # p's ancestor set() is their lowest common ancestor.
    while q not in ancestors:
        q = parent[q]
    return q

执行用时 :88 ms
内存消耗 :18 MB

时间复杂度:O(n),在最坏的情况下,我们可能会访问二叉树的所有节点
空间复杂度:O(n),在堆栈使用的最坏情况下,每个节点的父指针字典和祖先集合的空间为 n,斜二叉树的高度可能为 n

如果将题目中的二叉树限定为二叉搜索树呢?

二叉搜索树(BST)的性质如下:

  • 节点 NN 左子树上的所有节点的值都小于等于节点 NN 的值
  • 节点 NN 右子树上的所有节点的值都大于等于节点 NN 的值
  • 左子树和右子树也都是 BST

解法 1

递归法,根据二叉搜索树的性质,我们可以肯定,最近公共祖先的值一定在 p 和 q 之间或恰好等于 p 或 q,所以可以从 root 节点开始迭代,若当前节点大于 p 和 q 则通过递归在左子树中搜索,若当前节点小于 p 和 q 则通过递归在右子树中搜索,若上述两个条件都不符合,则当前节点是第一个值恰好在 p 和 q 之间的节点,或者就是 p 或 q 节点的其中一个,且另一个在当前节点的左子树或右子树中,那么当前节点即为 p 和 q 的最近公共祖先,如下图所示:

def lowest_common_ancestor(root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
    if p.val < root.val > q.val:
            return lowest_common_ancestor(root.left, p, q)
    if p.val > root.val < q.val:
            return lowest_common_ancestor(root.right, p, q)
    return root

执行用时 :88 ms
内存消耗 :17.9 MB

时间复杂度:O(n),其中 n 为 BST 中节点的个数,在最坏的情况下我们可能需要访问 BST 中所有的节点。
空间复杂度:O(n),所需开辟的额外空间主要是递归栈产生的,n 是 BST 的高度。

解法 2

和解法 1 原理一样,也可以用迭代实现。

def lowest_common_ancestor(root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
    while True:
        if p.val < root.val > q.val:
            root = root.left
        elif p.val > root.val < q.val:
            root = root.right
        else:
            return root

执行用时 :84 ms
内存消耗 :17.9 MB

时间复杂度:O(n),其中 n 为 BST 中节点的个数,在最坏的情况下我们可能需要遍历 BST 中所有的节点。
空间复杂度:O(1)

参考

https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/
https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容