@laigary.com~/interview/coding/236-lowest-common-an….md$
$ cat ./coding/236-lowest-common-ancestor-of-a-binary-tree.md
[Coding]·2023-01-31·8 min read

236. Lowest Common Ancestor of a Binary Tree

236. Lowest Common Ancestor of a Binary Tree

給一棵二元樹和其中兩個節點 pq,找出它們最近的共同祖先。題目保證兩個節點都存在於樹中。

這題是整個 LCA 家族的原型,其他四題都是在它上面改條件。

思路

LCA 的定義是「同時是 pq 的祖先,而且最深的那一個」。從上往下找很難判斷誰最深,但從下往上回報就很自然 —— 這是後序遍歷的形狀。

關鍵是先把遞迴的回傳值定義清楚:

lowestCommonAncestor(node, p, q) 回傳「node 的子樹裡,找到的最有用的那個節點」 —— 如果 pq 都在這棵子樹裡,回傳它們的 LCA;如果只找到其中一個,就回傳那一個;都沒找到回傳 None

把回傳值的意義用一句話講死,是所有樹形遞迴的第一步(見 Tree 遍歷模板)。定義好之後,四種情況就自己冒出來了:

情況回傳
root 本身就是 pqroot
左右子樹回報了一個root —— p、q 分居兩側,我就是答案
只有一邊有回報把那一邊的結果往上傳
兩邊都沒有None

第二種情況就是整題的核心:左右各找到一個,那分岔點就是我。

「自己就是 p 或 q 就直接回傳」為什麼安全

這是唯一需要停下來想的地方。假設 pq 的祖先,走到 p 時我們直接回傳了 p沒有繼續往下確認 q 在不在。這樣對嗎?

對,但前提是題目保證了 pq 都存在於樹中。既然 q 一定存在,而我們又已經站在 p 上,那 q 只可能在 p 的子樹裡 —— 所以 p 就是 LCA,不需要驗證。

這個前提在 1644 被拿掉了(pq 可能根本不在樹裡),所以那題就不能提早回傳,必須走完整棵樹。這是整個家族裡最重要的一個分界。

解題方向

# 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':
        if not root:
            return None
        if p == root or q == root:
            return root

        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)

        if left and right:
            return root
        if not left and not right:
            return None
        return left or right

return left or right 把「只有一邊有」和「哪一邊」兩件事合成一行 —— or 會回傳第一個為真的值,兩邊都是 None 時回傳 None。所以中間那個 if not left and not right: return None 其實可以省掉,寫出來只是讓四種情況一一對應,讀起來比較清楚。

比較是用 ==(也就是物件同一性),不是比 val。題目給的 pq 是節點參考,樹裡也可能有重複的值,所以不能改成比值。

補充

整個 LCA 家族:五題的骨架都是上面這一份,差別只在題目改了什麼條件。

題目條件的差異解法的差異
236一般二元樹,pq 保證存在後序回報(原型)
235. LCA of a BSTBST可以用值域直接往下走,O(h) 時間、O(1) 空間
1644. LCA IIpq 可能不存在不能提早回傳,要走完整棵樹確認
1650. LCA IIIparent 指標,但拿不到 root退化成兩條鏈結串列求相交
1676. LCA IV給的是一組節點而非兩個root in nodes 取代 root == p or root == q

同樣是「後序回傳一個值」的樹形遞迴124. Binary Tree Maximum Path Sum543. Diameter of Binary Tree250. Count Univalue Subtrees。共同點都是「先問清楚回傳值代表什麼」,程式碼才寫得下去。

複雜度

  • 時間 O(n) — 最壞要走遍每個節點
  • 空間 O(h) — 遞迴堆疊,h 是樹高;平衡樹是 O(logn),斜樹退化成 O(n)

其中 n 是節點數。