每日一练:LeeCode-236、二叉树的最近公共祖先【二叉树+DFS+从下往上】

本文是力扣每日一练:LeeCode-236、二叉树的最近公共祖先【二叉树+DFS+从下往上】 学习与理解过程,本文仅做学习之用,对本题感兴趣的小伙伴可以出门左拐LeeCode。

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

示例 1:
每日一练:LeeCode-236、二叉树的最近公共祖先【二叉树+DFS+从下往上】_第1张图片

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。

示例 2:
每日一练:LeeCode-236、二叉树的最近公共祖先【二叉树+DFS+从下往上】_第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的最近公共祖先。

每日一练:LeeCode-236、二叉树的最近公共祖先【二叉树+DFS+从下往上】_第3张图片

情况⼆:
每日一练:LeeCode-236、二叉树的最近公共祖先【二叉树+DFS+从下往上】_第4张图片
其实 情况⼀ 和 情况⼆ 代码实现过程都是⼀样的,也可以说,实现情况⼀的逻辑,顺便包含了情况⼆。
因为遇到 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后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)

那么为什么要遍历整棵树呢?直观上来看,找到最近公共祖先,直接⼀路返回就可以了。

每日一练:LeeCode-236、二叉树的最近公共祖先【二叉树+DFS+从下往上】_第5张图片

就像图中⼀样直接返回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返回呢?
如图
每日一练:LeeCode-236、二叉树的最近公共祖先【二叉树+DFS+从下往上】_第6张图片
图中节点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;
        }

那么寻找最⼩公共祖先,完整流程图如下:
每日一练:LeeCode-236、二叉树的最近公共祖先【二叉树+DFS+从下往上】_第7张图片
从图中,⼤家可以看到,我们是如何回溯遍历整棵⼆叉树,将结果返回给头结点的!

整体代码

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;
        }
    }
}

最重要的一句话:做二叉树的题目,首先需要确认的是遍历顺序

你可能感兴趣的:(#,每日一道LeeCode算法题,算法,数据结构,leetcode)