Lowest Common Ancestor

Introduction

 
In this article, we will find the lowest common ancestor node for a given node in a binary tree as well as from a binary search tree. A "common ancestor" for two nodes is a node that has those two nodes as descendants and a node can be a descendant of itself. The "lowest" common ancestor is the node that's closest to the two nodes that satisfies the common ancestor condition.
 

Lowest Common Ancestor In Binary Search Tree

 
Given the root of a binary search tree and two nodes in the tree, left and right, find the lowest common ancestor of p and q. For example, in the following diagram if p is node 2 and q is node 8, then the LowestCommonAncestor(p, q) is node 6. [ Leetcode Question ]
 
 
Here is how we approach to this problem,
  1. The current node is p and q is on the left.
  2. The current node is p and q is on the right.
  3. The current node is q and p is on the left.
  4. The current node is q and p is on the right.
  5. p is on the left and q is on the right (or vice versa).
We'll take advantage of the search property of binary search trees. In a BST, a node's left children have values that are less than the node's value, and the node's right children have values that are greater than the node's value. Because of this, we'll know if the p is on the left and q is on the right without having to search those subtrees. The C# solution for the problem is given below,
  1. /** 
  2.  * Definition for a binary tree node. 
  3.  * public class TreeNode { 
  4.  *     public int val; 
  5.  *     public TreeNode left; 
  6.  *     public TreeNode right; 
  7.  *     public TreeNode(int x) { val = x; } 
  8.  * } 
  9.  */  
  10.   
  11. public class Solution {  
  12.     public TreeNode LowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {  
  13.         // iterate through the tree starting from root  
  14.         TreeNode current = root;  
  15.         while (current == null)  
  16.         {  
  17.             // either iterate on left(p) or right(q) from the current node  
  18.             if (p.val < current.val)  
  19.                 current = current.left;  
  20.             else  
  21.                 current = current.right;  
  22.   
  23.             // lowest common ancestor found  
  24.             if ((p.val <= current.val && q.val >= current.val) ||  
  25.                (q.val <= current.val && p.val >= current.val))  
  26.                 return current;  
  27.         }  
  28.   
  29.         // lowest common ancestor not found  
  30.         return null;  
  31.     }  
  32. }  
The time complexity of the above algorithm is O(N), or rather, to be specific O(H) where H is the height of the tree. And the space complexity for the same is O(1) because we're not using any extra space, whereas the recursive solution has a space complexity of O(N).
 

Lowest Common Ancestor In Binary Tree

 
Given the root of a binary tree and two nodes in the tree, left and right, find the lowest common ancestor of p and q. For example, in the following diagram if p is node 5 and q is node 1, then the LowestCommonAncestor(p, q) is node 3. [ Leetcode Question ]
 
 
Given below is the algorithm to this problem,
  1. The current node is p and q is on the left.
  2. The current node is p and q is on the right.
  3. The current node is q and p is on the left.
  4. The current node is q and p is on the right.
  5. p is on the left and q is on the right (or vice versa). 
However, this time we have a regular binary tree, and so we're forced to look through the entire tree until we find p and q. Our strategy is, for each node, recursively check the left and right subtrees to determine if either subtree has p or q in it. The node that satisfies one of these conditions is the lowest common ancestor. Given below is the C# code for the above approach,
  1. /** 
  2.  * Definition for a binary tree node. 
  3.  * public class TreeNode { 
  4.  *     public int val; 
  5.  *     public TreeNode left; 
  6.  *     public TreeNode right; 
  7.  *     public TreeNode(int x) { val = x; } 
  8.  * } 
  9.  */  
  10. public class Solution {  
  11.     public TreeNode LowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {        
  12.         TreeNode index = null;  
  13.         DoesNodeHasQ(root, p, q, index);  
  14.         return index;  
  15.     }  
  16.     bool DoesNodeHasQ(TreeNode current, TreeNode p, TreeNode q, TreeNode index)  {  
  17.         if (current == null)  
  18.          return false;   
  19.   
  20.         // does left subtree have p or q  
  21.         bool leftSubtreeHasNodeQ = DoesNodeHasQ(current.left, p, q, index);  
  22.   
  23.         // does right subtree have p or q  
  24.         bool rightSubtreeHasNodeQ = DoesNodeHasQ(current.right, p, q, index);  
  25.   
  26.         // lowest common ancestor found   
  27.         if ((leftSubtreeHasNodeQ && (current == p)) ||  
  28.             (leftSubtreeHasNodeQ && (current == q)) ||  
  29.             (rightSubtreeHasNodeQ && (current == p)) ||  
  30.             (rightSubtreeHasNodeQ && (current == q)) ||  
  31.             (leftSubtreeHasNodeQ && rightSubtreeHasNodeQ))  
  32.             index = current;  
  33.           
  34.         // return true if the current node is p or q or a descendent of p or q  
  35.         return (current == p) ||  
  36.             (current == q) ||  
  37.             leftSubtreeHasNodeQ ||  
  38.             rightSubtreeHasNodeQ;  
  39.     }  
  40. }  
The time complexity for the above algorithm is O(N), because our recursion visits each node exactly once. And the space complexity is O(N) because we potentially hold the entire tree in memory at the same time and moreover the iterative solution requires a stack, hence increasing the space complexity.