Lowest Common Ancestor in Binary Tree in DSA Java - 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 time it takes?
Analyze the time complexity of the following code snippet.
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) {
return root;
}
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if (left != null && right != null) {
return root;
}
return left != null ? 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 number of operations grows roughly in direct proportion to the number of nodes.
Time Complexity: O(n)
This means the time to find the lowest common ancestor grows linearly with the number of nodes in the tree.
[X] Wrong: "The function only searches part of the tree, so it runs faster than visiting all nodes."
[OK] Correct: The recursion explores both left and right subtrees fully in the worst case, so it visits all nodes.
Understanding this linear time complexity helps you explain why this approach is efficient and expected when working with binary trees.
"What if the tree was a binary search tree and we used its ordering property? 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: Clarify the options
The deepest node that is an ancestor of both nodes correctly describes the LCA. The root node is not always the LCA, and the smallest value or child relationships do not define LCA.Final Answer:
The deepest node that is an ancestor of both nodes -> Option BQuick Check:
LCA = deepest common ancestor [OK]
- Confusing LCA with root node
- Thinking LCA is the smaller value node
- Assuming LCA is a child of given nodes
Solution
Step 1: Analyze method parameters and return type
The method should take the root node and two node values (or nodes) to find their LCA. It returns the TreeNode representing the LCA.Step 2: Match with options
public TreeNode findLCA(TreeNode root, int n1, int n2) matches this: it takes root and two integers, returns TreeNode. public int findLCA(int root, TreeNode n1, TreeNode n2) has wrong parameter types, C misses root, D returns void which is incorrect.Final Answer:
public TreeNode findLCA(TreeNode root, int n1, int n2) -> Option DQuick Check:
Correct signature includes root and returns TreeNode [OK]
- Omitting root parameter
- Wrong return type (void or int)
- Incorrect parameter order or types
3 / \ 5 1 / \ / \ 6 2 0 8 / \ 7 4 TreeNode lca = findLCA(root, 5, 1); System.out.println(lca.val);
Solution
Step 1: Identify nodes 5 and 1 in the tree
Node 5 is left child of root 3, node 1 is right child of root 3.Step 2: Find their lowest common ancestor
The lowest common ancestor of 5 and 1 is the root node 3, as it is the deepest node that has both as descendants.Final Answer:
3 -> Option AQuick Check:
LCA(5,1) = 3 [OK]
- Choosing one of the nodes instead of their ancestor
- Confusing child nodes as LCA
- Ignoring tree structure
public TreeNode findLCA(TreeNode root, int n1, int n2) {
if (root == null) return null;
if (root.val == n1 || root.val == n2) return root;
TreeNode left = findLCA(root.left, n1, n2);
TreeNode right = findLCA(root.right, n1, n2);
if (left != null && right != null) return root;
if (left != null) return left;
return right;
}Solution
Step 1: Understand the method logic
The method returns root if it matches either node, then recursively searches left and right subtrees. If both sides return non-null, root is LCA.Step 2: Identify missing case
The method assumes both nodes exist. If one node is missing, it may return the other node incorrectly as LCA without confirming presence of both.Final Answer:
It does not handle the case when one node is not present in the tree -> Option CQuick Check:
Missing node presence check causes incorrect LCA [OK]
- Ignoring missing node cases
- Assuming both nodes always exist
- Not validating subtree results properly
Solution
Step 1: Understand the problem with duplicates
When values are duplicated, comparing node values is unreliable to identify nodes uniquely.Step 2: Use node references for comparison
Instead of comparing values, compare the actual node references (memory addresses) to find exact nodes during recursion.Step 3: Adjust algorithm accordingly
Modify the LCA method to check if current node == target node references, not values.Final Answer:
Use node references directly instead of comparing values during recursion -> Option AQuick Check:
Use references, not values, for duplicates [OK]
- Comparing values instead of references
- Trying to remove duplicates unnecessarily
- Using value-based hash maps causing errors
Ready to prove it?
Pass the test and this pattern moves unknown
