Lowest Common Ancestor of a Binary Tree Part II | LeetCode


Given a binary tree, find the lowest common ancestor of two given nodes in the tree. Each node contains a parent pointer which links to its parent.
This problem is equivalent to finding the first common node in two intersected lists.
The best solution:A little creativity is needed here. Since we have the parent pointer, we could easily get the distance (height) of both nodes from the root. Once we knew both heights, we could subtract from one another and get the height’s difference (dh). If you observe carefully from the previous solution, the node which is closer to the root is alwaysdh steps ahead of the deeper node. We could eliminate the need of marking visited nodes altogether. Why?
The reason is simple, if we advance the deeper node dh steps above, both nodes would be at the same depth. Then, we advance both nodes one level at a time. They would then eventually intersect at one node, which is the LCA of both nodes.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int getHeight(Node *p) {
  int height = 0;
  while (p) {
    height++;
    p = p->parent;
  }
  return height;
}
// As root->parent is NULL, we don't need to pass root in.
Node *LCA(Node *p, Node *q) {
  int h1 = getHeight(p);
  int h2 = getHeight(q);
  // swap both nodes in case p is deeper than q.
  if (h1 > h2) {
    swap(h1, h2);
    swap(p, q);
  }
  // invariant: h1 <= h2.
  int dh = h2 - h1;
  for (int h = 0; h < dh; h++)
    q = q->parent;
  while (p && q) {
    if (p == q) return p;
    p = p->parent;
    q = q->parent;
  }
  return NULL;  // p and q are not in the same tree
}
An easy solution:
As we trace the two paths from both nodes up to the root, eventually it will merge into one single path. The LCA is the exact first intersection node where both paths merged into a single path. An easy solution is to use a hash table which records visited nodes as we trace both paths up to the root. Once we reached the first node which is already marked as visited, we immediately return that node as the LCA.

The run time complexity of this approach is O(h), where h is the tree’s height. The space complexity is also O(h), since it can mark at most 2h nodes
A variation of this problem which seemed to be more popular is:
Given two singly linked lists and they both intersect at one point (ie, forming a Y shaped list). Find where the two linked lists merge.

public static void swap(TreeNode a, TreeNode b) {
    final TreeNode temp = a;
    a = b;
    b = temp;
}

public static void swap(int a, int b) {
    final int temp = a;
    a = b;
    b = temp;
}

public static int getHeight(TreeNode p) {
    int height = 0;
    while (p != null) {
        height++;
        p = p.parent;
    }
    return height;
}

// As root->parent is NULL, we don't need to pass root in.
TreeNode LCA(TreeNode p, TreeNode q) {
    final int h1 = getHeight(p);
    final int h2 = getHeight(q);
    // swap both nodes in case p is deeper than q.
    if (h1 > h2) {
        swap(h1, h2);
        swap(p, q);
    }
    // invariant: h1 <= h2.
    final int dh = h2 - h1;
    for (int h = 0; h < dh; h++) {
        q = q.parent;
    }
    while (p != null && q != null) {
        if (p == q) {
            return p;
        }
        p = p.parent;
        q = q.parent;
    }
    return null; // p and q are not in the same tree
}
Without parent pointer
One first idea is to start from the root and recursively traverse down until we hit ether of the nodes then-
  1. If we found both nodes in the left subtree then recurse the above procedure starting from the left child of root.
  2. Else if, we found both nodes in the right subtree then recurse the above procedure starting from the right child of root.
  3. Else if we found one on left and other on right then return root.
  4. else if we didn’t find one or both then return null.
This above algorithm is a top down approach and the search spaces are overlapping i.e. we are traversing some part of the tree many times. We can skip overlapping search space by traversing from  bottom up as follows.
  1. While traversing from the bottom recursively if we reach a one of the given nodes, we pass it up to its parent.
  2. Then we will check from the parent whether either of the nodes is in left or right subtree.
  3. If it is then the parent must be the LCA. We pass its parent up to the root.
  4. If not then we pass up to the parent the root of the subtree (left or right) node which contains either one of the two nodes, or NULL if neither subtree contains the nodes .
public static BTNode LCA(BTNode root, BTNode x, BTNode y) {
   if (root == null) return null;
   if (root == x || root == y) return root;

   BTNode leftSubTree = LCA(root.left, x, y);
   BTNode rightSubTree = LCA(root.right, x, y);

   //x is in one subtree and and y is on other subtree of root
   if (leftSubTree != null && rightSubTree != null) return root;  
   //either x or y is present in one of the subtrees of root or none present in either side of the root
   return leftSubTree!=null ? leftSubTree : rightSubTree;  
}

LCA of BST
public static TreeNode LowestCommonAnchestorBST(TreeNode root, final TreeNode p, final TreeNode q) {
    if (root == null) {
        return null;
    }

    while (root != null) {
        if (root.key > p.key && root.key > q.key) {
            root = root.left;
        } else if (root.key < p.key && root.key < q.key) {
            root = root.right;
        } else {
            return root;
        }
    }
    return null;
}
This approach will take 0( d) time, where d is the depth of the deeper node.
TreeNode commonAncestor(TreeNode p, TreeNode q) {
        int delta= depth(p) - depth(q); // get difference in depths
        TreeNode first = delta > 0 ? q : p; // get shallower node
        TreeNode second= delta > 0 ? p : q; // get deeper node
        second= goUpBy(second, Math.abs(delta)); // move deeper node up

        /* Find where paths intersect. */
        while (first != second && first != null && second != null) {
                first= first.parent;
                second= second.parent;
        }
        return first== null || second ==  null ? null : first;
}
TreeNode goUpBy(TreeNode node, int delta) {
        while (delta> 0 && node != null) {
                node= node.parent;
                delta--;
        }
        return node;
}

int depth(TreeNode node) {
        int depth= 0;
        while (node != null) {
                node = node.parent;
                depth++;
        }
        return depth;

}

Solution #2: With Links to Parents (Better Worst-Case Runtime)

This algorithm takes O(t) time, where tis the size of the subtree for the first common ancestor. In the
worst case, this will be O( n), where n is the number of nodes in the tree. We can derive this runtime by
noticing that each node in that subtree is searched once.
TreeNode commonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        /* Check if either node is not in the tree, or if one covers the other. */
        if (!covers(root, p) || !covers(root, q)) {
                return null;
        }else if (covers(p, q)) {
                return p;
        }  else if (covers(q, p)) {
                return q;
        }

        /* Traverse upwards until you find a node that covers q. */
        TreeNode sibling= getSibling(p);
        TreeNode parent= p.parent;
        while (!covers(sibling, q)) {
                sibling= getSibling(parent);
                parent= parent.parent;
        }
        return parent;
}

boolean covers(TreeNode root, TreeNode p) {
        if (root== null) return false;
        if (root == p) return true;
        return covers(root.left, p) || covers(root.right, p);
}

TreeNode getSibling(TreeNode node) {
        if (node== null || node.parent == null) {
                return null;
        }
        TreeNode parent = node.parent;
        return parent.left== node ? parent.right : parent.left;
}

Labels

LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

Popular Posts