🧠
Brute Force (Check Paths and Compare)
💡 This approach exists to build intuition by explicitly finding paths from root to each node and then comparing them. It is straightforward but inefficient, helping beginners understand the problem structure.
Intuition
Find the path from root to p and root to q separately, then compare these paths to find the last common node.
Algorithm
- Traverse the tree to find the path from root to node p and store it.
- Traverse the tree to find the path from root to node q and store it.
- Compare the two paths node by node until they differ.
- The last common node before the difference is the LCA.
💡 This algorithm is easy to visualize but requires extra space and repeated traversals, which can be inefficient for large trees.
from typing import Optional, List
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def lowestCommonAncestor(root: Optional[TreeNode], p: TreeNode, q: TreeNode) -> Optional[TreeNode]:
def find_path(node: Optional[TreeNode], target: TreeNode, path: List[TreeNode]) -> bool:
if not node:
return False
path.append(node)
if node == target:
return True
if find_path(node.left, target, path) or find_path(node.right, target, path):
return True
path.pop()
return False
path_p, path_q = [], []
find_path(root, p, path_p)
find_path(root, q, path_q)
i = 0
while i < len(path_p) and i < len(path_q) and path_p[i] == path_q[i]:
i += 1
return path_p[i-1] if i > 0 else None
# Driver code example
if __name__ == '__main__':
# Build example tree
root = TreeNode(3)
root.left = TreeNode(5)
root.right = TreeNode(1)
root.left.left = TreeNode(6)
root.left.right = TreeNode(2)
root.left.right.left = TreeNode(7)
root.left.right.right = TreeNode(4)
root.right.left = TreeNode(0)
root.right.right = TreeNode(8)
p = root.left # Node 5
q = root.right # Node 1
lca = lowestCommonAncestor(root, p, q)
print(lca.val) # Expected output: 3
Line Notes
def find_path(node: Optional[TreeNode], target: TreeNode, path: List[TreeNode]) -> bool:Helper function to find path from root to target node
if not node:Base case: reached null node, path not found
path.append(node)Add current node to path as we traverse down
if node == target:If current node is target, path found
if find_path(node.left, target, path) or find_path(node.right, target, path):Recurse left and right to find target
path.pop()Backtrack if target not found in this path
find_path(root, p, path_p)Find path from root to p
find_path(root, q, path_q)Find path from root to q
while i < len(path_p) and i < len(path_q) and path_p[i] == path_q[i]:Compare paths to find last common node
return path_p[i-1] if i > 0 else NoneReturn last common node or None if no common ancestor
import java.util.*;
class TreeNode {
int val;
TreeNode left, right;
TreeNode(int x) { val = x; }
}
public class Solution {
public boolean findPath(TreeNode root, TreeNode target, List<TreeNode> path) {
if (root == null) return false;
path.add(root);
if (root == target) return true;
if (findPath(root.left, target, path) || findPath(root.right, target, path)) return true;
path.remove(path.size() - 1);
return false;
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
List<TreeNode> pathP = new ArrayList<>();
List<TreeNode> pathQ = new ArrayList<>();
findPath(root, p, pathP);
findPath(root, q, pathQ);
int i = 0;
while (i < pathP.size() && i < pathQ.size() && pathP.get(i) == pathQ.get(i)) {
i++;
}
return i > 0 ? pathP.get(i - 1) : null;
}
public static void main(String[] args) {
Solution sol = new Solution();
TreeNode root = new TreeNode(3);
root.left = new TreeNode(5);
root.right = new TreeNode(1);
root.left.left = new TreeNode(6);
root.left.right = new TreeNode(2);
root.left.right.left = new TreeNode(7);
root.left.right.right = new TreeNode(4);
root.right.left = new TreeNode(0);
root.right.right = new TreeNode(8);
TreeNode p = root.left; // 5
TreeNode q = root.right; // 1
TreeNode lca = sol.lowestCommonAncestor(root, p, q);
System.out.println(lca.val); // Expected: 3
}
}
Line Notes
public boolean findPath(TreeNode root, TreeNode target, List<TreeNode> path) {Helper to find path from root to target node
if (root == null) return false;Base case: null node means path not found
path.add(root);Add current node to path
if (root == target) return true;Found target node
if (findPath(root.left, target, path) || findPath(root.right, target, path)) return true;Recurse left and right
path.remove(path.size() - 1);Backtrack if target not found here
findPath(root, p, pathP);Find path to p
findPath(root, q, pathQ);Find path to q
while (i < pathP.size() && i < pathQ.size() && pathP.get(i) == pathQ.get(i)) {Compare paths to find last common node
return i > 0 ? pathP.get(i - 1) : null;Return LCA or null
#include <iostream>
#include <vector>
using namespace std;
struct TreeNode {
int val;
TreeNode *left, *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
bool findPath(TreeNode* root, TreeNode* target, vector<TreeNode*>& path) {
if (!root) return false;
path.push_back(root);
if (root == target) return true;
if (findPath(root->left, target, path) || findPath(root->right, target, path)) return true;
path.pop_back();
return false;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
vector<TreeNode*> pathP, pathQ;
findPath(root, p, pathP);
findPath(root, q, pathQ);
int i = 0;
while (i < pathP.size() && i < pathQ.size() && pathP[i] == pathQ[i]) {
i++;
}
return i > 0 ? pathP[i - 1] : nullptr;
}
int main() {
TreeNode* root = new TreeNode(3);
root->left = new TreeNode(5);
root->right = new TreeNode(1);
root->left->left = new TreeNode(6);
root->left->right = new TreeNode(2);
root->left->right->left = new TreeNode(7);
root->left->right->right = new TreeNode(4);
root->right->left = new TreeNode(0);
root->right->right = new TreeNode(8);
TreeNode* p = root->left; // 5
TreeNode* q = root->right; // 1
TreeNode* lca = lowestCommonAncestor(root, p, q);
cout << lca->val << endl; // Expected: 3
return 0;
}
Line Notes
bool findPath(TreeNode* root, TreeNode* target, vector<TreeNode*>& path) {Helper to find path from root to target
if (!root) return false;Base case: null node means no path
path.push_back(root);Add current node to path
if (root == target) return true;Found target node
if (findPath(root->left, target, path) || findPath(root->right, target, path)) return true;Recurse left and right
path.pop_back();Backtrack if target not found
findPath(root, p, pathP);Find path to p
findPath(root, q, pathQ);Find path to q
while (i < pathP.size() && i < pathQ.size() && pathP[i] == pathQ[i]) {Compare paths to find last common node
return i > 0 ? pathP[i - 1] : nullptr;Return LCA or nullptr
class TreeNode {
constructor(val=0, left=null, right=null) {
this.val = val;
this.left = left;
this.right = right;
}
}
function findPath(root, target, path) {
if (!root) return false;
path.push(root);
if (root === target) return true;
if (findPath(root.left, target, path) || findPath(root.right, target, path)) return true;
path.pop();
return false;
}
function lowestCommonAncestor(root, p, q) {
const pathP = [];
const pathQ = [];
findPath(root, p, pathP);
findPath(root, q, pathQ);
let i = 0;
while (i < pathP.length && i < pathQ.length && pathP[i] === pathQ[i]) {
i++;
}
return i > 0 ? pathP[i - 1] : null;
}
// Driver code example
const root = new TreeNode(3);
root.left = new TreeNode(5);
root.right = new TreeNode(1);
root.left.left = new TreeNode(6);
root.left.right = new TreeNode(2);
root.left.right.left = new TreeNode(7);
root.left.right.right = new TreeNode(4);
root.right.left = new TreeNode(0);
root.right.right = new TreeNode(8);
const p = root.left; // 5
const q = root.right; // 1
const lca = lowestCommonAncestor(root, p, q);
console.log(lca.val); // Expected: 3
Line Notes
function findPath(root, target, path) {Helper to find path from root to target
if (!root) return false;Base case: null node means no path
path.push(root);Add current node to path
if (root === target) return true;Found target node
if (findPath(root.left, target, path) || findPath(root.right, target, path)) return true;Recurse left and right
path.pop();Backtrack if target not found
findPath(root, p, pathP);Find path to p
findPath(root, q, pathQ);Find path to q
while (i < pathP.length && i < pathQ.length && pathP[i] === pathQ[i]) {Compare paths to find last common node
return i > 0 ? pathP[i - 1] : null;Return LCA or null
TimeO(n) to find each path, so O(n) + O(n) = O(n)
SpaceO(n) for storing paths and recursion stack
We traverse the tree twice to find paths, each traversal can visit all nodes in worst case. Paths can be up to height of tree, O(n) in worst case.
💡 For n=20 nodes, this means up to 40 node visits and storing two paths of length up to 20.
Interview Verdict: Accepted but inefficient for large trees
This approach works but uses extra space and repeated traversals, so it's not optimal for big inputs.