Lowest Common Ancestor of a Binary Tree

Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree.

According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes p and q as the lowest node in T that has both p and q as descendants (where we allow a node to be a descendant of itself).”

Example 1:

Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
Output: 3
Explanation: The LCA of nodes 5 and 1 is 3.

Example 2:

Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
Output: 5
Explanation: The LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition.

Example 3:

Input: root = [1,2], p = 1, q = 2
Output: 1

Constraints:

  • The number of nodes in the tree is in the range [2, 10<sup>5</sup>].

  • -10<sup>9</sup> <= Node.val <= 10<sup>9</sup>

  • All Node.val are unique.

  • p != q

  • p and q will exist in the tree.

How to solve this one?

Naive Approach:

To find the LCA we store the root to Node path using the path function in an ArrayList, we run this function. for both p and q. Once we've stored the path into an ArrayList, we can traverse over all the nodes in both lists and the last common element would be the LCA.

class Solution {

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        ArrayList<TreeNode> p1 = new ArrayList<>();
        ArrayList<TreeNode> p2 = new ArrayList<>();
        path(root, p, p1);
        path(root, q, p2);
        int i = 0;
        while(i<p1.size() && i<p2.size() && p1.get(i) == p2.get(i)){
            i++;
        }
        return p1.get(i-1);
    }
    public boolean path(TreeNode root, TreeNode node, ArrayList<TreeNode> list){
        if(root == null) return false;
        list.add(root);
        if(root == node) return true;
        if(path(root.left, node, list) || path(root.right, node, list)){
            return true;
        }
        list.remove(list.size()-1);//Taaki faltu chars na store ho :P
        return false;
    }   
}

But this isn't space-optimized. Let's look at a space-optimized approach.

Efficient Way To Solve :

Looking at this problem might seem hard at first.

Now to solve it let's consider a node, and breakdown the problem into a few cases:
Case 1: The node we're considering is null, now obviously we won't be able to reach anywhere with a null node, so just return null;

Case 2: if the node we're holding is either p or q, if we have found p, Now this is the case "where we allow a node to be a descendant of itself". this node is the LCA here

Case 3: Now if the above two cases do not work let's try considering the left and right subtree of this node, here if LeftSubtree(l in the code) returns any of p or q (definitely not null) and the right subtree provides returns any of p or q (opposite to left subtree and not null), the node would be the LCA.

Case 4: when if either of the subtrees returns a not-null value and the other provides a null value, our problem reduces to that tree only, hence just returning the root of the subtree).

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null) return null; // Case 1
        if(root == p || root == q) return root; // Case 2
        TreeNode l = lowestCommonAncestor(root.left, p, q);
        TreeNode r = lowestCommonAncestor(root.right, p, q);

        if(l!=null && r!=null) return root; // Case 3
        else // Case 4
        {
            if(l!=null) return l;
            else return r;
        }
    }

Another Method :

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || p == root || q == root){
            return root;
        }
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);

        if(left == null)
            return right;
        else if(right == null)
            return left;
        else
            return root;
    }