本文是力扣每日一练:LeeCode-236、二叉树的最近公共祖先【二叉树+DFS+从下往上】 学习与理解过程,本文仅做学习之用,对本题感兴趣的小伙伴可以出门左拐LeeCode。
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
示例 2:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。
示例 3:
输入:root = [1,2], p = 1, q = 2
输出:1
提示:
- 树中节点数目在范围 [2, 10^5] 内。
- -10^9 <= Node.val <= 10^9
- 所有 Node.val 互不相同 。
- p != q
- p 和 q 均存在于给定的二叉树中。
思路
本题思路参考 代码随想录 题解
递归法
这道题使用递归,就必须要从下往上查找,才能方便找到公共祖先
这道题如何判断⼀个节点是节点q和节点p的公共祖先是关键:
情况⼀:
如果找到⼀个节点,发现左⼦树出现结点p,右⼦树出现节点q,或者 左⼦树出现结
点q,右⼦树出现节点p,那么该节点就是节点p和q的最近公共祖先。
情况⼆:
其实 情况⼀ 和 情况⼆ 代码实现过程都是⼀样的,也可以说,实现情况⼀的逻辑,顺便包含了情况⼆。
因为遇到 q 或者 p 就返回,这样也包含了 q 或者 p 本身就是 公共祖先的情况。
1、确定递归函数返回值以及参数
需要递归函数返回值,来告诉我们是否找到节点q或者p,那么返回值为bool类型就可以了。
但我们还要返回最近公共节点,可以利⽤题⽬中返回值是TreeNode,那么如果遇到p或者q,就把q或者p返回,返回值不为空,就说明找到了q或者p。
TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q)
2、确定终⽌条件
遇到空的话,因为树都是空了,所以返回空。
那么我们来说⼀说,如果 root == q,或者 root == p,说明找到 q p ,则将其返回,这个返回值,后⾯在中节点的处理过程中会⽤到。
if (root==p || root==q || root==null)return root;
3、确定单层递归逻辑
本题函数有返回值,是因为回溯的过程需要递归函数的返回值做判断,但本题我们依然要遍历树的所有节点
如果递归函数有返回值,如何区分要搜索⼀条边,还是搜索整个树呢?
搜索⼀条边的写法:
if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;
搜索整个树写法:
left = 递归函数(root->left); // 左
right = 递归函数(root->right); // 右
left与right的逻辑处理; // 中
区别:
在递归函数有返回值的情况下:
- 如果要搜索⼀条边,递归函数返回值不为空的时候,⽴刻返回
- 如果搜索整个树,直接⽤⼀个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)。
那么为什么要遍历整棵树呢?直观上来看,找到最近公共祖先,直接⼀路返回就可以了。
就像图中⼀样直接返回7,多美滋滋。
但事实上还要遍历根节点右⼦树(即使此时已经找到了⽬标节点了),也就是图中的节点4、15、20。
因为在如下代码的后序遍历中,如果想利⽤left和right做逻辑处理, 不能⽴刻返回,⽽是要等left与right逻辑处理完之后才能返回。
left = 递归函数(root->left); // 左
right = 递归函数(root->right); // 右
left与right的逻辑处理; // 中
所以此时⼤家要知道我们要遍历整棵树。知道这⼀点,对本题就有⼀定深度的理解了。
那么先⽤left和right接住左⼦树和右⼦树的返回值,代码如下:
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
如果left 和 right都不为空,说明此时root就是最近公共节点。这个⽐较好理解
如果left为空,right不为空,就返回right,说明⽬标节点是通过right返回的,反之依然。
这⾥有的同学就理解不了了,为什么left为空,right不为空,⽬标节点通过right返回呢?
如图
图中节点10的左⼦树返回null,右⼦树返回⽬标值7,那么此时节点10的处理逻辑就是把右⼦树的返回值(最近公共祖先7)返回上去!
那么如果left和right都为空,则返回left或者right都是可以的,也就是返回空。
代码如下:
if (root==p || root==q || root==null)return root;
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
if (left==null){
return right;
} else if (right==null) {
return left;
}else{
return root;
}
那么寻找最⼩公共祖先,完整流程图如下:
从图中,⼤家可以看到,我们是如何回溯遍历整棵⼆叉树,将结果返回给头结点的!
整体代码
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root==p || root==q || root==null)return root;
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
if (left==null){
return right;
} else if (right==null) {
return left;
}else{
return root;
}
}
}
最重要的一句话:做二叉树的题目,首先需要确认的是遍历顺序