Lowest Common Ancestor in Binary Tree in DSA C - Time & Space Complexity
We want to understand how the time needed to find the lowest common ancestor grows as the tree gets bigger.
How does the search through the tree affect the total work done?
Analyze the time complexity of the following code snippet.
struct Node {
int val;
struct Node *left, *right;
};
struct Node* lowestCommonAncestor(struct Node* root, struct Node* p, struct Node* q) {
if (root == NULL || root == p || root == q) return root;
struct Node* left = lowestCommonAncestor(root->left, p, q);
struct Node* right = lowestCommonAncestor(root->right, p, q);
if (left && right) return root;
return left ? left : right;
}
This code finds the lowest common ancestor of two nodes in a binary tree by searching both sides recursively.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Recursive calls visiting each node once.
- How many times: Each node is visited once during the recursion.
As the tree grows, the function visits every node once to check for the target nodes.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 visits |
| 100 | About 100 visits |
| 1000 | About 1000 visits |
Pattern observation: The work grows linearly with the number of nodes in the tree.
Time Complexity: O(n)
This means the time to find the ancestor grows directly with the number of nodes in the tree.
[X] Wrong: "The function only visits part of the tree, so it runs faster than O(n)."
[OK] Correct: The function may need to check every node to find both targets, so it visits all nodes in the worst case.
Understanding this time complexity helps you explain how recursive tree searches work and why they take time proportional to tree size.
"What if the tree was a binary search tree and you could use node values to guide the search? How would the time complexity change?"
Practice
Solution
Step 1: Understand the definition of LCA
The Lowest Common Ancestor is the deepest node in the tree that has both given nodes as descendants (a node can be a descendant of itself).Step 2: Differentiate from other nodes
The root node is not always the LCA unless both nodes are in different subtrees of the root. The smallest value or traversal order does not define LCA.Final Answer:
The deepest node that is an ancestor of both nodes -> Option CQuick Check:
LCA = deepest common ancestor [OK]
- Confusing LCA with root node
- Choosing node based on value instead of position
- Using traversal order to find LCA
Solution
Step 1: Identify the base case for recursion
When the current node is NULL, it means we reached the end of a path without finding the target nodes, so return NULL.Step 2: Check other options
Returning root when left or right child is NULL is incorrect because the node itself may not be the LCA. Returning root when root is NULL is invalid.Final Answer:
if (root == NULL) return NULL; -> Option BQuick Check:
Base case: root NULL returns NULL [OK]
- Returning root instead of NULL when node is NULL
- Checking only left or right child for base case
- Returning root when root is NULL
struct Node {
int val;
struct Node *left, *right;
};
struct Node* findLCA(struct Node* root, int n1, int n2) {
if (root == NULL) return NULL;
if (root->val == n1 || root->val == n2) return root;
struct Node* left_lca = findLCA(root->left, n1, n2);
struct Node* right_lca = findLCA(root->right, n1, n2);
if (left_lca && right_lca) return root;
return (left_lca != NULL) ? left_lca : right_lca;
}
// Tree structure:
// 3
// / \
// 5 1
// / \ / \
// 6 2 0 8
// / \
// 7 4
// Call: findLCA(root, 5, 1);
What node value will the function return?Solution
Step 1: Trace recursive calls for nodes 5 and 1
Starting at root (3), neither 5 nor 1 matches root. Recursively check left subtree (root 5) and right subtree (root 1). Left subtree returns node 5, right subtree returns node 1.Step 2: Determine LCA based on returned nodes
Since both left_lca and right_lca are non-NULL, the current root (3) is the LCA.Final Answer:
3 -> Option AQuick Check:
LCA(5,1) = 3 [OK]
- Returning one of the nodes instead of their ancestor
- Confusing node values with LCA
- Not checking both left and right subtree results
Solution
Step 1: Analyze the function's behavior
The function returns NULL if it does not find either node in a subtree. If one node is missing in the tree, the function may return NULL even if the other node exists.Step 2: Identify missing existence check
The function assumes both nodes exist. Without verifying both nodes are present, it can return NULL incorrectly.Final Answer:
The function does not check if both nodes exist in the tree before finding LCA -> Option DQuick Check:
Check node existence before LCA [OK]
- Ignoring node existence check
- Assuming base case handles all scenarios
- Not handling identical nodes input
Solution
Step 1: Understand the problem with duplicates
When values are not unique, identifying nodes by value alone can lead to incorrect LCA because multiple nodes share the same value.Step 2: Use unique identifiers for nodes
Using unique memory addresses or pointers ensures the function finds the exact nodes intended, avoiding confusion from duplicates.Final Answer:
Modify the function to find nodes by their unique memory addresses or pointers instead of values -> Option AQuick Check:
Use unique node references for duplicates [OK]
- Assuming values are unique identifiers
- Using traversal order to pick nodes
- Ignoring duplicates causing wrong LCA
Ready to prove it?
Pass the test and this pattern moves unknown
