|
| 1 | +<h1 align='center'>Lowest - Common - Ancestor - Of a - Binary - Tree</h1> |
| 2 | + |
| 3 | +## Problem Statement |
| 4 | + |
| 5 | +**Problem URL :** [Lowest Common Ancestor of a Binary Tree](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/) |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | + |
| 10 | +## Problem Explanation |
| 11 | +**Problem:** Given a binary tree, find the lowest common ancestor (LCA) of two given nodes, `p` and `q`. The LCA of two nodes in a binary tree is the lowest node that has both `p` and `q` as descendants (either direct children or via other nodes). |
| 12 | + |
| 13 | +**Constraints:** |
| 14 | +- Each node in the tree has a unique value. |
| 15 | +- Both nodes `p` and `q` exist in the tree. |
| 16 | + |
| 17 | +**Real-World Analogy:** |
| 18 | +Imagine a family tree where each node represents a person. The lowest common ancestor of two family members is the closest common ancestor, such as a grandparent or great-grandparent. Similarly, in the binary tree, the LCA is the nearest common node that `p` and `q` descend from. |
| 19 | + |
| 20 | +**Edge Cases to Consider:** |
| 21 | +1. **Root node is `p` or `q`:** If `root` is one of the two nodes, then it is automatically the LCA. |
| 22 | +2. **Nodes in separate subtrees:** `p` and `q` may be located in different subtrees of the root. |
| 23 | +3. **Nodes are the same:** If `p` and `q` are the same node, that node is the LCA. |
| 24 | + |
| 25 | +### Step 2: Approach |
| 26 | + |
| 27 | +**High-Level Overview:** |
| 28 | +To find the LCA, we can use a recursive depth-first search (DFS) strategy: |
| 29 | +1. Traverse from the root to find `p` and `q`. |
| 30 | +2. When both `p` and `q` are found in separate subtrees of the same node, that node is the LCA. |
| 31 | +3. If `p` or `q` matches the root node, then root is their LCA. |
| 32 | + |
| 33 | +**Detailed Steps:** |
| 34 | +1. **Base Case:** If the current `root` is `NULL`, return `NULL` since there is no node here. |
| 35 | +2. **Check for `p` or `q`:** If `root` is equal to `p` or `q`, return `root`. |
| 36 | +3. **Recursive Search:** |
| 37 | + - Recurse down the left and right subtrees to search for `p` and `q`. |
| 38 | + - If one node is found in each subtree, the current `root` is the LCA. |
| 39 | +4. **Return Results:** |
| 40 | + - If both left and right return non-`NULL` values, then `root` is the LCA. |
| 41 | + - If only one subtree returns a node, propagate that node up as the potential LCA. |
| 42 | + |
| 43 | +**Pseudocode:** |
| 44 | +```plaintext |
| 45 | +function LCA(root, p, q): |
| 46 | + if root is NULL: |
| 47 | + return NULL |
| 48 | + |
| 49 | + if root is p or root is q: |
| 50 | + return root |
| 51 | + |
| 52 | + left = LCA(root.left, p, q) |
| 53 | + right = LCA(root.right, p, q) |
| 54 | + |
| 55 | + if left is not NULL and right is not NULL: |
| 56 | + return root |
| 57 | + |
| 58 | + return left if left is not NULL else right |
| 59 | +``` |
| 60 | +## Problem Solution |
| 61 | +```cpp |
| 62 | +/** |
| 63 | + * Definition for a binary tree node. |
| 64 | + * struct TreeNode { |
| 65 | + * int val; |
| 66 | + * TreeNode *left; |
| 67 | + * TreeNode *right; |
| 68 | + * TreeNode(int x) : val(x), left(NULL), right(NULL) {} |
| 69 | + * }; |
| 70 | + */ |
| 71 | +class Solution { |
| 72 | +public: |
| 73 | + TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { |
| 74 | + if(root == NULL) return NULL; |
| 75 | + |
| 76 | + if(root -> val == p -> val || root -> val == q -> val) return root; |
| 77 | + |
| 78 | + TreeNode* leftAns = lowestCommonAncestor(root -> left, p, q); |
| 79 | + TreeNode* rightAns = lowestCommonAncestor(root -> right, p, q); |
| 80 | + |
| 81 | + if(leftAns != NULL && rightAns != NULL) return root; |
| 82 | + else if(leftAns != NULL && rightAns == NULL) return leftAns; |
| 83 | + else if(leftAns == NULL && rightAns != NULL) return rightAns; |
| 84 | + else return NULL; |
| 85 | + } |
| 86 | +}; |
| 87 | +``` |
| 88 | + |
| 89 | +## Problem Solution Explanation |
| 90 | + |
| 91 | +Here’s the code with line-by-line explanation: |
| 92 | + |
| 93 | +1. **Function Definition and Parameters** |
| 94 | + ```cpp |
| 95 | + TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { |
| 96 | + ``` |
| 97 | + - This function is a method of the `Solution` class. |
| 98 | + - It takes three parameters: |
| 99 | + - `root`: A pointer to the current node (starting with the root of the tree). |
| 100 | + - `p`: A pointer to one of the target nodes in the tree. |
| 101 | + - `q`: A pointer to the other target node. |
| 102 | + - The function will return a pointer to the node that is the lowest common ancestor of `p` and `q`. |
| 103 | +
|
| 104 | +2. **Line 1: Base Case** |
| 105 | + ```cpp |
| 106 | + if (root == NULL) return NULL; |
| 107 | + ``` |
| 108 | + - **Purpose:** This line handles the base case. If `root` is `NULL` (i.e., we’ve reached a leaf node or an empty tree), it returns `NULL`. |
| 109 | + - **Reason:** If `root` is `NULL`, there’s no node here, so it can’t be the ancestor of `p` or `q`. |
| 110 | + |
| 111 | +3. **Line 2: Check if `root` is a Target Node** |
| 112 | + ```cpp |
| 113 | + if (root->val == p->val || root->val == q->val) return root; |
| 114 | + ``` |
| 115 | + - **Purpose:** Check if the current node (`root`) is equal to either `p` or `q`. |
| 116 | + - **Explanation:** |
| 117 | + - If the current `root` node matches the value of `p` or `q`, this node is either `p` or `q`. |
| 118 | + - Since one of our target nodes has been found, we return `root` as a potential candidate for the LCA. |
| 119 | + - **Example:** If `root` is `p`, this means `p` could potentially be the lowest common ancestor if `q` is found further down the tree. |
| 120 | + |
| 121 | +4. **Line 3: Recursive Search in the Left Subtree** |
| 122 | + ```cpp |
| 123 | + TreeNode* leftAns = lowestCommonAncestor(root->left, p, q); |
| 124 | + ``` |
| 125 | + - **Purpose:** Recursively search for nodes `p` and `q` in the left subtree. |
| 126 | + - **Explanation:** |
| 127 | + - This line makes a recursive call to `lowestCommonAncestor`, but with `root->left` as the new root node. |
| 128 | + - The result, `leftAns`, will be `NULL` if neither `p` nor `q` is found in the left subtree. |
| 129 | + - Otherwise, it will contain the pointer to `p`, `q`, or their common ancestor found in the left subtree. |
| 130 | + |
| 131 | +5. **Line 4: Recursive Search in the Right Subtree** |
| 132 | + ```cpp |
| 133 | + TreeNode* rightAns = lowestCommonAncestor(root->right, p, q); |
| 134 | + ``` |
| 135 | + - **Purpose:** Similar to the previous step, this line searches for nodes `p` and `q` in the right subtree. |
| 136 | + - **Explanation:** |
| 137 | + - A recursive call is made with `root->right` as the new root. |
| 138 | + - The result, `rightAns`, will store `NULL` if neither `p` nor `q` is found in the right subtree. |
| 139 | + - Otherwise, it contains the pointer to `p`, `q`, or their LCA within the right subtree. |
| 140 | + |
| 141 | +6. **Line 5: Determine if Current `root` is the LCA** |
| 142 | + ```cpp |
| 143 | + if (leftAns != NULL && rightAns != NULL) return root; |
| 144 | + ``` |
| 145 | + - **Purpose:** Check if nodes `p` and `q` were found in different subtrees of the current `root`. |
| 146 | + - **Explanation:** |
| 147 | + - If both `leftAns` and `rightAns` are non-`NULL`, then one target node was found in each subtree. |
| 148 | + - This means that the current node, `root`, is the lowest node that has both `p` and `q` as descendants. |
| 149 | + - Therefore, we return `root` as the lowest common ancestor. |
| 150 | + |
| 151 | +7. **Line 6: Propagate the Left Result Upwards** |
| 152 | + ```cpp |
| 153 | + else if (leftAns != NULL && rightAns == NULL) return leftAns; |
| 154 | + ``` |
| 155 | + - **Purpose:** If only `leftAns` is non-`NULL`, return `leftAns` as the LCA candidate. |
| 156 | + - **Explanation:** |
| 157 | + - If only `leftAns` is non-`NULL`, then either `p` or `q` (or their common ancestor) was found in the left subtree, while the right subtree has no relevant nodes. |
| 158 | + - We propagate `leftAns` upwards as a potential LCA since `p` and `q` could still be descendants of the node in `leftAns`. |
| 159 | + |
| 160 | +8. **Line 7: Propagate the Right Result Upwards** |
| 161 | + ```cpp |
| 162 | + else if (leftAns == NULL && rightAns != NULL) return rightAns; |
| 163 | + ``` |
| 164 | + - **Purpose:** Similarly, if only `rightAns` is non-`NULL`, return `rightAns`. |
| 165 | + - **Explanation:** |
| 166 | + - This means that either `p` or `q` (or their LCA) was found in the right subtree. |
| 167 | + - Since the left subtree has no relevant nodes, we return `rightAns` as the LCA candidate for the upper levels of recursion. |
| 168 | + |
| 169 | +9. **Line 8: Neither `p` nor `q` Found** |
| 170 | + ```cpp |
| 171 | + else return NULL; |
| 172 | + ``` |
| 173 | + - **Purpose:** If both `leftAns` and `rightAns` are `NULL`, return `NULL` as no LCA was found. |
| 174 | + - **Explanation:** |
| 175 | + - This means that neither `p` nor `q` is present in the current subtree rooted at `root`. |
| 176 | + - Returning `NULL` signifies that `p` and `q` are not in this subtree. |
| 177 | + |
| 178 | + |
| 179 | +### Step 4: Output Examples |
| 180 | + |
| 181 | +1. **Example 1:** |
| 182 | + - **Input:** Binary tree root = [3,5,1,6,2,0,8,null,null,7,4], `p` = 5, `q` = 1 |
| 183 | + - **Output:** 3 |
| 184 | + - **Explanation:** Nodes 5 and 1 have the common ancestor 3, which is their LCA. |
| 185 | + |
| 186 | +2. **Example 2:** |
| 187 | + - **Input:** Binary tree root = [3,5,1,6,2,0,8,null,null,7,4], `p` = 5, `q` = 4 |
| 188 | + - **Output:** 5 |
| 189 | + - **Explanation:** Since node 5 is an ancestor of node 4, 5 itself is the LCA. |
| 190 | + |
| 191 | +3. **Example 3:** |
| 192 | + - **Input:** Binary tree root = [1,2], `p` = 1, `q` = 2 |
| 193 | + - **Output:** 1 |
| 194 | + - **Explanation:** Node 1 is the root and an ancestor of node 2, so it’s the LCA. |
| 195 | + |
| 196 | +### Step 5: Time and Space Complexity |
| 197 | + |
| 198 | +1. **Time Complexity:** \(O(N)\), where \(N\) is the number of nodes in the tree. In the worst case, each node is visited once to locate `p` and `q`. |
| 199 | + |
| 200 | +2. **Space Complexity:** \(O(H)\), where \(H\) is the height of the tree. The recursive call stack could go as deep as the height of the tree in an unbalanced tree, making it \(O(H)\). For a balanced tree, the space complexity would be \(O(\log N)\), but for a skewed tree, it would be \(O(N)\). |
| 201 | + |
| 202 | +**Optimizations:** This recursive solution is already optimal for this problem. For trees that are extremely skewed, an iterative solution using parent pointers could reduce stack usage, but it generally won’t improve the time complexity. |
0 commit comments