# time O(N) space O(H) H: stack
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
# time O(N) space O(N)
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
""" tc O(N) sc O(H)
main idea: 3 cases -- (1) p is parent of q (2) q is parent of p (3) there is common parent of p, q
1. termination condition: current root node is None/ current root is node p or node q
2. for current root node recursively go deeper of the tree, get return val from its subtree
3. based on if p, q come from the same subtree, return different LCA accordingly
"""
if not root:
return None
if root == p or root == q :
return root
leftParent = self.lowestCommonAncestor(root.left,p,q)
rightParent = self.lowestCommonAncestor(root.right,p,q)
if leftParent and rightParent:
return root
elif rightParent:
return rightParent
elif leftParent :
return leftParent
iterative solution: stack + hashMap + Set
# time O(N) space O(N)
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
"""
1. inorder traversal to get p and q, record each node's parent node
2. start from node p and cache its all ancestor
3. back trace node q's ancestor and check if its ancestor appeared in p's ancestor
tc O(N) sc O(N)
"""
st = [root]
parent = {root:None}
while not(p in parent and q in parent):
node = st.pop()
if node.left:
st.append(node.left)
parent[node.left] = node
if node.right:
parent[node.right] = node
st.append(node.right)
seen = set()
cur = p
while cur:
seen.add(cur)
cur = parent[cur]
cur2 = q
while cur2 not in seen:
cur2 = parent[cur2]
return cur2
2 path cache + 1 stack(store path and node) slow solution
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
"""
1.cornor case
2. use stack to cache path and parent node, in order traverse both p ,q
3. start from root, find the first appeared node in both pathp and pathq
"""
if not root or root == p or root == q:
return root
st = [(root,[root])]
pathp, pathq = [],[]
while not(pathp and pathq):
node,path = st.pop()
if node == p:
pathp = path[:]
if node == q:
pathq = path[:]
if node.left:
st.append((node.left,path+[node.left]))
if node.right:
st.append((node.right,path+[node.right]))
i,n = 0, min(len(pathq),len(pathp))
while i < n and pathq[i] is pathp[i]:
res = pathq[i]
i += 1
return res
optimization using 1 stack to record all path for both p and q path
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
"""
assuming all nodes are unique
1.use DFS to search for p and q, and record both p,q's path,
2.compare two paths and find the lowest common ancestor
"""
st,ppath,qpath = [],[],[]
while True:
if root.left:
st.append(root)
root.left,root = None, root.left
elif root.right:
st.append(root)
root.right,root = None, root.right
else:
if root == p:
ppath = st[:]
ppath.append(root)
if root == q:
qpath = st[:]
qpath.append(root)
if ppath and qpath:
break
root = st.pop()
i,len_path = 0, min(len(ppath),len(qpath))
while i < len_path and ppath[i] is qpath[i]:
res = ppath[i]
i += 1
return res