二叉树的最近公共祖先
链接:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/
题目大意:求p和q在root树上的公共祖先,p和q可以是自身的祖先
方法1:
归根结底还是分解成子问题,然后递归求解
针对树上的每一个非pq节点X,pq节点的分布有以下四种情况:
- 情况1:p和q在X的左右子树上,一边一个
- 情况2:p和q都在X的左子树left上
- 情况3:p和q都在X的右子树right上
- 情况4:p和q既不在X的右子树上,也不在X的左子树上,在X的上面
我们可以定义一个dfs(root,p,q) 返回p,q的最近公共祖先,针对以上四种情况,分别采取以下措施:
- 针对情况1:既然p和q在X的左右子树上,那么X就是p和q的最近公共祖先
- 针对情况2:返回X的左子树left,left是p和q的最近公共祖先
- 针对情况3:返回X的右子树right,right是p和q的最近公共祖先
- 针对情况4:返回nil,因为p和q的最近公共子树还在X的上面,是X的祖先
// 求解p,q在root中的最近公共祖先
func dfs(root, p, q *TreeNode) *TreeNode {
// 自身也是自己的公共祖先
if root == nil || root == p || root == q {
return root
}
// dfs 从底部向上递归
left := dfs(root.Left, p, q) // 求出p和q在root左子树上的最近公共组件
right := dfs(root.Right, p, q) //求出p和q在root右子树上的最近公共组件
// 判断 left和right的四种情况
if left == nil && right != nil { // p q分布在root右侧 right是p,q的最近公共祖先
return right
} else if right == nil && left != nil { // p q 分布在root左侧,left是p,q的最近公共祖先
return left
} else if right == nil && left == nil { // p q 不在root两侧,在root上面,返回nil
return nil
} else if right != nil && left != nil { // p q 分布在root两侧,说明root是最近公共祖先
return root
}
// 不会走到这里,上面那么写是为了更好理清思路
return root
}
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
return dfs(root, p, q)
}
性能分析:
时间复杂度:O(N)
- 其中 NN 是二叉树的节点数。二叉树的所有节点有且只会被访问一次,从 p 和 q 节点往上跳经过的祖先节点个数不会超过 NN,因此总的时间复杂度为 O(N)
空间复杂度:O(H)
- 递归树深度,递归调用的栈深度取决于二叉树的高度,二叉树最坏情况下为一条链,此时高度为 N,因此空间复杂度为 O(N)
方法2:
用map记录每个节点的父节点
遍历p的所有祖宗节点(通过记录了所有节点的父节点的map逐渐向上跳),包括自己,遍历过的就标记一下
然后遍历q的所有祖宗节点,包括自己,遇到遍历过的标记节点,那么此节点就是pq的最近公共祖宗节点
一直到遍历到根节点如果还没有遇到遍历过的标记节点,那么他们pq的最近公共祖先就是root
var tmap map[*TreeNode]*TreeNode
// 记录每个节点的父亲节点
func dfs(root *TreeNode){
if root==nil{
return
}
if root.Left!=nil{
tmap[root.Left]=root
dfs(root.Left)
}
if root.Right!=nil{
tmap[root.Right]=root
dfs(root.Right)
}
}
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
tmap=make(map[*TreeNode]*TreeNode)
dfs(root)
// 标记p的所有祖宗节点
flag:=make(map[*TreeNode]int)
curp:=p
for curp!=root{
flag[curp]=1
curp=tmap[curp]
}
// 遍历q的所有祖宗节点,遇到标记过的节点,那么该节点就是最近公共祖宗节点
curq:=q
for curq!=root{
if _,ok:=flag[curq];ok{
return curq
}else {
curq=tmap[curq]
}
}
// 特殊情况,root是他们的最近公共祖先节点
return root
}
性能分析:
时间复杂度:O(N)
空间复杂度:O(N)