🧠
Brute Force (Find Paths and Compare)
💡 This approach exists to build intuition by explicitly finding paths to both nodes 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, which is the LCA.
Algorithm
- Traverse the BST from root to find the path to node p and store it.
- Traverse the BST from root to find the path to node q and store it.
- Compare the two paths node by node until they differ.
- Return the last common node found in both paths as the LCA.
💡 This algorithm is easy to visualize but requires extra space and repeated traversals, which can be inefficient for large trees.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def find_path(root, target, path):
if not root:
return False
path.append(root)
if root.val == target.val:
return True
if (root.left and find_path(root.left, target, path)) or (root.right and find_path(root.right, target, path)):
return True
path.pop()
return False
def lowestCommonAncestor(root, p, q):
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]
# Example usage:
# Construct BST nodes
# root = TreeNode(6)
# ... build tree ...
# print(lowestCommonAncestor(root, p, q).val)
Line Notes
def find_path(root, target, path):Defines helper to find path from root to target node
if not root:Base case: reached leaf without finding target
path.append(root)Add current node to path as we go down
if root.val == target.val:Check if current node is target
if (root.left and find_path(root.left, target, path)) or (root.right and find_path(root.right, target, path))Recursively search left or right subtree
path.pop()Backtrack if target not found in this path
find_path(root, p, path_p)Find path to node p
find_path(root, q, path_q)Find path to node 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]Return the last common ancestor node
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.val == target.val) return true;
if ((root.left != null && findPath(root.left, target, path)) || (root.right != null && 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 pathP.get(i - 1);
}
// main method and tree construction can be added for testing
}
Line Notes
public boolean findPath(TreeNode root, TreeNode target, List<TreeNode> path)Helper to find path from root to target
if (root == null) return false;Base case: no node found
path.add(root);Add current node to path
if (root.val == target.val) return true;Check if current node is target
if ((root.left != null && findPath(root.left, target, path)) || (root.right != null && findPath(root.right, target, path)))Search left or right subtree recursively
path.remove(path.size() - 1);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.get(i) == pathQ.get(i))Compare paths to find last common node
return pathP.get(i - 1);Return LCA node
#include <iostream>
#include <vector>
using namespace std;
struct TreeNode {
int val;
TreeNode *left, *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
bool findPath(TreeNode* root, TreeNode* target, vector<TreeNode*>& path) {
if (!root) return false;
path.push_back(root);
if (root->val == target->val) return true;
if ((root->left && findPath(root->left, target, path)) || (root->right && 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 pathP[i - 1];
}
// main function and tree construction can be added for testing
Line Notes
bool findPath(TreeNode* root, TreeNode* target, vector<TreeNode*>& path)Helper to find path from root to target node
if (!root) return false;Base case: reached null node
path.push_back(root);Add current node to path
if (root->val == target->val) return true;Check if current node is target
if ((root->left && findPath(root->left, target, path)) || (root->right && findPath(root->right, target, path)))Recursively search left or right subtree
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 pathP[i - 1];Return LCA node
function TreeNode(val, 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.val === target.val) return true;
if ((root.left && findPath(root.left, target, path)) || (root.right && 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 pathP[i - 1];
}
// Example usage:
// let root = new TreeNode(6);
// ... build tree ...
// console.log(lowestCommonAncestor(root, p, q).val);
Line Notes
function findPath(root, target, path)Helper to find path from root to target
if (!root) return false;Base case: no node found
path.push(root);Add current node to path
if (root.val === target.val) return true;Check if current node is target
if ((root.left && findPath(root.left, target, path)) || (root.right && findPath(root.right, target, path)))Recursively search left or right subtree
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 pathP[i - 1];Return LCA node
TimeO(n) in worst case, where n is number of nodes, because we may traverse the tree twice to find paths
SpaceO(n) for storing paths and recursion stack
Finding each path can take O(n) time in worst case, and storing paths requires O(n) space. Comparing paths is O(n) as well.
💡 For n=20 nodes, this means up to 40 node visits plus storing paths, which is inefficient for large trees.
Interview Verdict: Accepted but inefficient; useful for understanding
This approach is easy to implement but not optimal. It helps beginners understand the problem before moving to better solutions.