236.二叉树的最近公共祖先
1、概要
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
说明:
- 所有节点的值都是唯一的。
- p、q 为不同节点且均存在于给定的二叉树中。
首先想的是要是能自底向上查找就好了,这样就可以找到公共祖先了。二叉树回溯的过程就是从低到上。
后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。
如何判断一个节点是节点q和节点p的公共祖先呢。
判断逻辑是 如果递归遍历遇到q,就将q返回,遇到p 就将p返回,那么如果 左右子树的返回值都不为空,说明此时的中节点,一定是q 和p 的最近祖先。容易忽略一个情况,就是节点本身p(q),它拥有一个子孙节点q(p)
2、思路
递归
- 确定递归函数返回值以及参数
需要返回,告诉是否找到两个节点,bool类型就可以。但还要返回最近公共节点,所以遇到p,q返回,不为空也说明找到了。
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q)
- 确定终止条件
遇到空,树空了,如果找到p,q,将其返回,中节点处理会用到。
if (root == null || root == p || root == q) { // 递归结束条件
return root;
}
- 确定单层递归逻辑
有返回值,回溯过程需要递归函数的返回值判断。需要遍历树的所有节点
在递归函数有返回值的情况下:如果要搜索一条边,递归函数返回值不为空的时候,立刻返回,如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)。
为什么要遍历整棵树呢?直观上来看,在左边找到最近公共祖先,直接一路返回就可以了。
但事实上还要遍历根节点右子树(即使此时已经找到了目标节点了)
因为在如下代码的后序遍历中,如果想利用left和right做逻辑处理, 不能立刻返回,而是要等left与right逻辑处理完之后才能返回。
如果left 和 right都不为空,说明此时root就是最近公共节点。这个比较好理解
如果left为空,right不为空,就返回right,说明目标节点是通过right返回的,反之依然。
图中节点10的左子树返回null,右子树返回目标值7,那么此时节点10的处理逻辑就是把右子树的返回值(最近公共祖先7)返回上去!
那么如果left和right都为空,则返回left或者right都是可以的,也就是返回空。
// 后序遍历
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if(left == null && right == null) { // 若未找到节点 p 或 q
return null;
}else if(left == null && right != null) { // 若找到一个节点
return right;
}else if(left != null && right == null) { // 若找到一个节点
return left;
}else { // 若找到两个节点
return root;
}
迭代
没有给出迭代法,因为迭代法不适合模拟回溯的过程。理解递归的解法就够了
3、代码
class Solution {
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){// 若未找到节点 p 或 q
return null;
}else if(left == null && right != null){ // 若找到一个节点,在右边
return right;
}else if(left != null && right == null){// 若找到一个节点,在左边
return left;
}else { // 若找到两个节点
return root;
}
}
}