Lowest Common Ancestor

凌雨尘發表於2024-05-12

Source

Given the root and two nodes in a Binary Tree. Find the lowest common ancestor(LCA) of the two nodes.

The lowest common ancestor is the node with largest depth which is the ancestor of both nodes.
Example
        4

    /     \

  3         7

          /     \

        5         6
For 3 and 5, the LCA is 4.

For 5 and 6, the LCA is 7.
For 6 and 7, the LCA is 7.

題解1 - 自底向上

初次接觸這種題可能會沒有什麼思路,在沒有思路的情況下我們就從簡單例子開始分析!首先看看3和5,這兩個節點分居根節點4的兩側,如果可以從子節點往父節點遞推,那麼他們將在根節點4處第一次重合;再來看看5和6,這兩個都在根節點4的右側,沿著父節點往上遞推,他們將在節點7處第一次重合;最後來看看6和7,此時由於7是6的父節點,故7即為所求。從這三個基本例子我們可以總結出兩種思路——自頂向下(從前往後遞推)和自底向上(從後往前遞推)。

順著上述例項的分析,我們首先看看自底向上的思路,自底向上的實現用一句話來總結就是——如果遍歷到的當前節點是 A/B 中的任意一個,那麼我們就向父節點彙報此節點,否則遞迴到節點為空時返回空值。具體來說會有如下幾種情況:

1、當前節點不是兩個節點中的任意一個,此時應判斷左右子樹的返回結果。

  • 若左右子樹均返回非空節點,那麼當前節點一定是所求的根節點,將當前節點逐層向前彙報。// 兩個節點分居樹的兩側
  • 若左右子樹僅有一個子樹返回非空節點,則將此非空節點向父節點彙報。// 節點僅存在於樹的一側
  • 若左右子樹均返回NULL, 則向父節點返回NULL. // 節點不在這棵樹中

2、當前節點即為兩個節點中的一個,此時向父節點返回當前節點。
根據此遞迴模型容易看出應該使用先序/後序遍歷來實現。

C++ Recursion From Bottom to Top

/**
 * Definition of TreeNode:
 * class TreeNode {
 * public:
 *     int val;
 *     TreeNode *left, *right;
 *     TreeNode(int val) {
 *         this->val = val;
 *         this->left = this->right = NULL;
 *     }
 * }
 */
class Solution {
public:
    /**
     * @param root: The root of the binary search tree.
     * @param A and B: two nodes in a Binary.
     * @return: Return the least common ancestor(LCA) of the two nodes.
     */
    TreeNode *lowestCommonAncestor(TreeNode *root, TreeNode *A, TreeNode *B) {
        // return either A or B or NULL
        if (NULL == root || root == A || root == B) return root;

        TreeNode *left = lowestCommonAncestor(root->left, A, B);
        TreeNode *right = lowestCommonAncestor(root->right, A, B);

        // A and B are on both sides
        if ((NULL != left) && (NULL != right)) return root;

        // either left or right or NULL
        return (NULL != left) ? left : right;
    }
};

Java

/**
 * Definition of TreeNode:
 * public class TreeNode {
 *     public int val;
 *     public TreeNode left, right;
 *     public TreeNode(int val) {
 *         this.val = val;
 *         this.left = this.right = null;
 *     }
 * }
 */
public class Solution {
    /**
     * @param root: The root of the binary search tree.
     * @param A and B: two nodes in a Binary.
     * @return: Return the least common ancestor(LCA) of the two nodes.
     */
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode A, TreeNode B) {
        if (root == null) return null;

        TreeNode lNode = lowestCommonAncestor(root.left, A, B);
        TreeNode rNode = lowestCommonAncestor(root.right, A, B);
        // root is the LCA of A and B
        if (lNode != null && rNode != null) return root;
        // root node is A/B(including the case below)
        if (root == A || root == B) return root;
        // return lNode/rNode if root is not LCA
        return (lNode != null) ? lNode : rNode;
    }
}

原始碼分析

結合例子和遞迴的整體思想去理解程式碼,在root == A || root == B後即層層上浮(自底向上),直至找到最終的最小公共祖先節點。

最後一行return (NULL != left) ? left : right;將非空的左右子樹節點和空值都包含在內了。

關於重複節點:由於這裡比較的是元素地址,因此可以認為樹中不存在重複元素,否則不符合樹的資料結構。

題解 - 自底向上(計數器)

為了解決上述方法可能導致誤判的情況,我們可以對返回結果新增計數器來解決。由於此計數器的值只能由子樹向上遞推,故應該用後序遍歷。在類中新增私有變數較為方便。

定義pair<TreeNode *, int> result(node, counter)表示遍歷到某節點時的返回結果,返回的node表示LCA 路徑中的可能的最小節點,相應的計數器counter則表示目前和A或者B匹配的節點數,若計數器為2,則表示已匹配過兩次,該節點即為所求,若只匹配過一次,還需進一步向上遞推。表述地可能比較模糊,還是看看程式碼吧。

C++

/**
 * Definition of TreeNode:
 * class TreeNode {
 * public:
 *     int val;
 *     TreeNode *left, *right;
 *     TreeNode(int val) {
 *         this->val = val;
 *         this->left = this->right = NULL;
 *     }
 * }
 */
class Solution {
public:
    /**
     * @param root: The root of the binary search tree.
     * @param A and B: two nodes in a Binary.
     * @return: Return the least common ancestor(LCA) of the two nodes.
     */
    TreeNode *lowestCommonAncestor(TreeNode *root, TreeNode *A, TreeNode *B) {
        if ((NULL == A) || (NULL == B)) return NULL;

        pair<TreeNode *, int> result = helper(root, A, B);

        if (A != B) {
            return (2 == result.second) ? result.first : NULL;
        } else {
            return (1 == result.second) ? result.first : NULL;
        }
    }

private:
    pair<TreeNode *, int> helper(TreeNode *root, TreeNode *A, TreeNode *B) {
        TreeNode * node = NULL;
        if (NULL == root) return make_pair(node, 0);

        pair<TreeNode *, int> left = helper(root->left, A, B);
        pair<TreeNode *, int> right = helper(root->right, A, B);

        // return either A or B
        int count = max(left.second, right.second);
        if (A == root || B == root)  return make_pair(root, ++count);

        // A and B are on both sides
        if (NULL != left.first && NULL != right.first) return make_pair(root, 2);

        // return either left or right or NULL
        return (NULL != left.first) ? left : right;
    }
};

Java

/**
 * Definition of TreeNode:
 * public class TreeNode {
 *     public int val;
 *     public TreeNode left, right;
 *     public TreeNode(int val) {
 *         this.val = val;
 *         this.left = this.right = null;
 *     }
 * }
 */
public class Solution {
    private int count = 0;
    /**
     * @param root: The root of the binary search tree.
     * @param A and B: two nodes in a Binary.
     * @return: Return the least common ancestor(LCA) of the two nodes.
     */
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode A, TreeNode B) {
        TreeNode result = helper(root, A, B);
        if (A == B) {
            return result;
        } else {
            return (count == 2) ? result : null;
        }
    }

    private TreeNode helper(TreeNode root, TreeNode A, TreeNode B) {
        if (root == null) return null;

        TreeNode lNode = helper(root.left, A, B);
        TreeNode rNode = helper(root.right, A, B);
        // root is the LCA of A and B
        if (lNode != null && rNode != null) return root;
        // root node is A/B(including the case below)
        if (root == A || root == B) {
            count++;
            return root;
        }
        // return lNode/rNode if root is not LCA
        return (lNode != null) ? lNode : rNode;
    }
}

原始碼分析

在A == B時,計數器返回1的節點即為我們需要的節點,否則只取返回2的節點,如此便保證了該方法的正確性。對這種實現還有問題的在下面評論吧。

相關文章