递归_二叉树_48 . 二叉树最近公共祖先查找

本节目标:

1 . 通过本示例,深刻理解"后序遍历"

2 . 加深"递归"思路模式:明确返回值含义找到切入点


题目介绍

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

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

cpp 复制代码
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        
    }
};

本文约字3300字,阅读+思考约 16 min

检验答案网址:236. 二叉树的最近公共祖先 - 力扣(LeetCode)

若有小伙伴未看过"二叉树"初篇,其实无碍------如果想更加熟悉作者的递归_二叉树逻辑:08 二叉树的中序遍历_二叉树中序遍历题目-CSDN博客


解析

1 . 本题需求也很明确:给你一棵二叉树(TreeNode* root)

2 . 要求返回 p 和 q 两个结点的最近公共祖先(这个祖先也是TreeNode*)

3 . 二叉树的题就是这样,树型结构决定了------一棵树可以用当前树的根节点表示,反过来:一个结点(地址)其实可以代表以该结点为根的树

4 . 二叉树------递归不分家

解释:

a. 首先确定p, q位置后

b. 从目标结点(p或是q)向上返回

c. 可以发现,p 和 q的结点恰好是p

递归_二叉树

1 . 这是现成的二叉树------递归思路

2 . 怎么递归呢?

确定好两样:核心任务 和 递归出口

a . 核心任务:对当前树的根节点进行的操作

b . 递归出口:对传入参数的合法性进行判断

3 . 首先是核心任务:

技巧------若直接无法推断核心任务,可结合传入的参数和返回值进行辅助推导

4 . 比如此处:

a . 言之要有物,这就把示例树拿出来

b . 对于当前树(比如结点3)就代表了整棵树,它拥有的参数:root , p , q

c . 无疑我能靠root 得到root->left root->right

d . 也能找到p , q 这两棵树(如果存在,一定能找到

5 . 继续上例的核心任务拆解:

a . 一棵树操作完毕后,返回值类型为TreeNode*

b . 若结合上图,最好返回的结点应该是找到的p,q的祖先/公共祖先

c . 似乎还不够清楚:但是我们能得到结论,我们很重视当前树的root->left 和 root->right的返回值

6 . 可以说,如果遇到root == p || root->q 我们就可以不再往下;毕竟找的是p 和 q的祖先而不是孩子

7 . 那么此时返回值无疑是root(也只有root这个变量是TreeNode*,能够返回)
步骤6 、7

8 . 现在来到结点2为根节点的树:现在它得到自己右孩子的返回值;那左孩子的返回值呢?图上看似没画,其实也很明显了

9 . 结点2的左孩子树的返回值应该是无用值,找来找去应当用nullptr
步骤8 、9

10 . 那么我问你?对于2结点,应该向上返回谁的地址呢?------是结点2的地址还是,结点2得到的结点4的地址

11 . 不纠结,先写一点代码:

cpp 复制代码
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == p || root == q)
            return root;//当找到p 或者 q 直接可以向上返回
        TreeNode* left_result = lowestCommonAncestor(root->left,q,p);
        TreeNode* right_result = lowestCommonAncestor(root->right,p,q);
        
        // ... 
        return nullptr// ?虽不知具体返回谁,但目标是返回p,q的公共祖先
    }

12 . 我们拿一个普遍的例子:(p 和 q的公共祖先是第三个结点)

13 . 根据代码:现在结点3(这棵树)得到了 来自左子树得到的TreeNode* 和 右孩子得到的TreeNode*

14 . 此时这棵树(以结点3为根)应该返回谁呢?从图上看出来了,应该返回root(也就是本结点)

15 . 对于这个现象,我们也很好理解:当左子树得到了p 的地址,右子树得到了q的地址(因为都是有效地址),所以最后返回的就是二者的root(p、q的公共祖先)
16 . 总结一下核心任务:当前节点在做决策前,已经拿到了左右子树的 "完整反馈"(left_result/right_result),知道左右子树是否找到 p/q------向上级返回情况

a . 若left_result//right_result 单边找到,很明显无法返回root。只能老实返回left_result//right_result,让上级找到另一边

b . 若left_result//right_result两边都找到,才能向上返回root(表示root就是这个公共结点)

cpp 复制代码
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == p || root == q)
            return root;
        TreeNode* left_result = lowestCommonAncestor(root->left,q,p);
        TreeNode* right_result = lowestCommonAncestor(root->right,p,q);
        
        // ... 
        if(left_result && right_result)
            return root;
        else if(left_result)
            return left_result;
        else return right_result;
    }
};

17 . 递归出口:root 不能为空

cpp 复制代码
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == nullptr)
            return nullptr;
        if(root == p || root == q)
            return root;
        TreeNode* left_result = lowestCommonAncestor(root->left,q,p);
        TreeNode* right_result = lowestCommonAncestor(root->right,p,q);
        
        if(left_result && right_result)
            return root;
        else if(left_result)
            return left_result;
        else return right_result;
    }
};

18 . 为何我说本节能加深理解"后序遍历"?

答:因为本题在考虑核心任务时,这切入点正是后序遍历得到的结果:

注:左 -> 右 -> 根

cpp 复制代码
TreeNode* left_result = lowestCommonAncestor(root->left,q,p);
TreeNode* right_result = lowestCommonAncestor(root->right,p,q);

19 . 破局的关键------如何抓住真正的切入点:即明确返回值的含义

注:这里的返回值应该是:p 和 q 的公共祖先(也很有可能是p 或者 q)

20 . 知晓返回值含义后,我们若要靠近目标------找到q,p的公共祖先,其实这返回值正好可以为我们提供信息。那么先后关系就有了:应该先得到左右子树关于p/q的反阔,再在本树下做决策

二叉树开篇之作:08 二叉树的中序遍历_二叉树中序遍历题目-CSDN博客 类似后序遍历之作:09 二叉树的最大深度-CSDN博客

总结以及完整参考代码

cpp 复制代码
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == nullptr)
            return nullptr;
        if(root == p || root == q)
            return root;
        TreeNode* left_result = lowestCommonAncestor(root->left,q,p);
        TreeNode* right_result = lowestCommonAncestor(root->right,p,q);
        
        if(left_result && right_result)
            return root;
        else if(left_result)
            return left_result;
        else return right_result;
    }
};

愿你的快乐没有终止条件,幸福的递归深度无限延伸,所有的烦恼都在回溯中释放,2026 马年,返回值全是好运!

相关推荐
0 0 08 小时前
CCF-CSP 39-2 水印检查(watermark)【C++】
c++·算法
plus4s8 小时前
2月15日(78,80,81题)
c++·算法·图论
能源系统预测和优化研究9 小时前
【原创改进代码】考虑碳交易与电网交互波动惩罚的共享储能电站优化配置与调度模型
算法·能源
935969 小时前
机考27 翻译21 单词14
c语言·数据结构·算法
回敲代码的猴子10 小时前
2月14日打卡
算法
blackicexs11 小时前
第四周第七天
算法
期末考复习中,蓝桥杯都没时间学了11 小时前
力扣刷题19
算法·leetcode·职场和发展
Renhao-Wan11 小时前
Java 算法实践(四):链表核心题型
java·数据结构·算法·链表
zmzb010312 小时前
C++课后习题训练记录Day105
开发语言·c++·算法