[algorithm] find the lowest common ancestor node of the two nodes of the binary tree

Posted by willchoong on Sun, 23 Jan 2022 06:53:15 +0100

Zuo Cheng cloud algorithm and data structure course https://www.bilibili.com/video/BV13g41157hK?p=2&spm_id_from=pageDriver

subject

Given the nodes node1 and node2 of two binary trees, find their lowest common ancestor node.

Problem solution

Solution I

Set a HashMap to save the node and its parent node (set the parent node of the root node as itself), then save all the ancestor nodes of node1 in a set set1, and seek the ancestor node of node2 to see whether it is in set1. If so, this node is the lowest common ancestor node.

//Premise: node1 and node2 must belong to the tree with head as the head
//Returns the lowest common ancestor of node1 and node2
public static Node lca(Node head, Node node1, Node node2) {
    HashMap<Node, Node> fatherMap = new HashMap<>(); //Save the node and its parent node
    fatherMap.put(head, head); //The parent node of the root node is itself
    process(head, fatherMap);  //Find the parent node of all nodes
    HashSet<Node> set1 = new HashSet<>(); //Collection of ancestor nodes of node1
    Node cur = node1;
    //Find the ancestor node of node1 and put it into set1 
    while (cur != fatherMap.get(cur)) { //Stop when tracing back to the root node
        set1.add(cur);
        cur = fatherMap.get(cur);
    }
    set1.add(head);

    //Find the ancestor node of node2
    cur = node2;
    while (!set1.contains(cur)) { //Stop when set1 contains this node
        cur = fatherMap.get(cur);
    }
    //This node is the lowest common ancestor node
    return cur;
}
//Recursively find the parent node of all nodes except the root node and save it in the fatherMap
private static void process(Node head, HashMap<Node, Node> fatherMap) {
    if (head == null) {
        return;
    }
    fatherMap.put(head.left, head);
    fatherMap.put(head.right, head);
    process(head.left, fatherMap);
    process(head.right, fatherMap);
}

Solution II

There are two situations in a binary tree:

  • If one node is a descendant of another node, the lowest common ancestor node is the node that is the ancestor
  • If there is no grandson relationship between the two nodes, the lowest common ancestor node is the nearest common ancestor

Imagine a recursive operation:

  • The basic event is to return the head node of a subtree when it is null or node1 or node2.
  • Recursion is performed on the left and right subtrees to obtain the return values of the left and right subtrees. There are only four possible return values of recursion: null, node1, node2, and the lowest common ancestor of the two nodes.
  • When the return value of the left subtree is not empty and the return value of the right subtree is not empty (that is, the two nodes fall on the left and right subtrees respectively), the head node is returned (the head node is the lowest common ancestor node).
  • When the condition that the return values of the left and right subtrees are not empty is not satisfied, if the return value of the left subtree is not empty, this value is returned (possibly node1 and node2, the lowest common ancestor node of the two nodes), otherwise, the value of the right subtree is returned (possibly null, node1 and node2, the lowest common ancestor node of the two nodes).

In this recursive operation, there are the following cases:

  • If a subtree does not contain node1 and node2, the value returned by its left and right subtrees must be null, and the value returned upward must also be null; (a)

  • If a subtree contains only one of node1 and node2, assuming that it contains node1, its upward return value is node1; (b)

  • If a subtree contains both node1 and node2,

    • If node1 and node2 have a grandson relationship, node1 and node2 can only exist in one of the left and right subtrees of the tree. Assuming node1 is the ancestor (node1 is the lowest common ancestor node), the return values of the left and right subtrees of the tree must be node1 and null. According to the last principle of recursive operation, whether the return value of the left subtree is node1 or the right subtree is node1, Can ensure that the tree returns node1 upward.

    • If node1 and node2 have no grandson relationship and fall into their left and right subtrees respectively, according to (b), the returned values of the left and right subtrees are node1 and node2 respectively, that is, if the returned values of the left and right subtrees are not empty, the value returned upward by the subtree is the head node. (c)

    • If node1 and node2 have no grandson relationship and both fall on one of the subtrees of the tree, assuming that they fall on the right subtree, there must be such a subtree in the subtree of the right subtree that meets the above condition (c). The return value of this subtree is the head node, that is, the lowest common ancestor node, and the final return value of the right subtree is also the lowest common node. If the left subtree meets the condition (a), null is returned. Therefore, the value returned upward by the tree is the lowest common ancestor node.

Through the above analysis, we can write the following beautiful code.

public static Node lowestCommonAncestor(Node head, Node node1, Node node2) {
    if (head == null || head == node1 || head == node2) { //base case
        return head;
    }
    Node left = lowestCommonAncestor(head.left, node1, node2);
    Node right = lowestCommonAncestor(head.right, node1, node2);
    if (left != null && right != null) {
        return head;
    }
    return left != null ? left : right;
}

Topics: Algorithm