剑指offer之二叉树的最近公共祖先

本文解析了二叉树和二叉搜索树中寻找最近公共祖先的问题,介绍了后序遍历、递归和迭代的方法,展示了如何利用二叉搜索树特性简化查找过程。涉及复杂度分析和代码实现。

写在前面的话:
剑指offer第二次刷题,希望每题都能秒了。从后向前记录下自己对于题目的理解。

二叉树的最近公共祖先

问题描述

在这里插入图片描述

问题分析

  1. 最近公共祖先:根据百科定义,可以解读出最近公共祖先有几种情况:1,p.q有共同的祖先并且祖先不是他们自己2,祖先是p或者q本身,3.p.q没有公共祖先
  2. 遍历方式:要求x的深度要尽可能大,那就后序遍历,最近的一个祖先就是深度最大的.后序遍历就是根节点的状态(返回值)需要由左右子树来确定.
  3. 如何处理:递归。递归三要素:终止条件,返回值,本次递归要做什么。此外,递归可以认为此时已经算出了节点,可以直接使用递归之后的值来进行模拟。
  4. 算法步骤:1.后序遍历从根节点一直遍历到最左边节点然后开始访问,如果当前节点为空,自然返回当前节点,同时如果当前节点==qorp中的一个,那这个节点肯定就是公共祖先本身,否则只能是左右孩子,那就不可能当前节点=porq。2.此时要结合递归三要素明确返回值和本次递归要做什么,首先,肯定要遍历左右孩子来找到指定的节点,结合题目本身知道返回值树节点本身。3.如果返回值的左右孩子都存在,意味着可能左孩子是p,右孩子是q,也可能左孩子是q,有孩子是p,但是总的来说,递归返回的节点一定存在,而且此时已经找到了指定节点和指定节点的最近公共祖先3+.如果递归返回的节点一个为空,那说明指定的两个节点肯定在相邻的兄弟树上,直接返回兄弟树首节点也就是当前递归返回值的另一个即可。

代码表述

/**
 * 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) {
        //树一般递归都能解决,但是要确定使用哪种遍历方式
        if(root==nullptr)return root;
        if(root==q||root==p)return root;
        TreeNode*l=lowestCommonAncestor(root->left,p,q);
        TreeNode*r=lowestCommonAncestor(root->right,p,q);
        if(l==p&&r==q||l==q&&r==p)return root;
        if(r==nullptr)return l;
        if(l==nullptr)return r;
        return nullptr;
    }
};

复杂度分析

时间复杂度: O(N),N为二叉树的节点个数。最差要访问二叉树的每个节点。
空间复杂度:当树退化成链表的时候有N层,递归就有N次,需要开辟O(N)的 空间。

二叉搜索树的最近公共祖先

问题描述

在这里插入图片描述

问题分析

  1. 二叉搜索树:左子树所有节点的值都小于其父节点的值,右子树所有节点的值都大于其父节点的值,因此中序遍历二叉搜索树也是有序的。
  2. 最近公共祖先问题:同二叉树的最近公共祖先一样,有几个点:1,p.q有共同的祖先并且祖先不是他们自己2,祖先是p或者q本身,3.p.q没有公共祖先。而结合二叉搜索树的特性,如果p.q有共同的祖先并且祖先不是他们自己那肯定就是当前的节点值在指定的两个节点之间,如果祖先是p或者q本身,那肯定就是当前节点等于指定节点的其中一个,这样当前节点就是分叉点,指定节点要么是在子树中,要么就是指定节点的其中一个就是当前节点。
  3. 二分思想:如果指定的节点都比当前节点大,那肯定就是在当前节点的右子树中去找,如果指定的节点都比当前节点小,那肯定最近公共祖先就在左子树中去找。如果不满足这个条件,就说明当前节点肯定是分叉点,直接返回当前节点即可,因为当前节点就是最近公共祖先了。

代码表述

/**
 * 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) {
        if(root==NULL)return root;
        if(p->val<root->val&&q->val<root->val){
            return lowestCommonAncestor(root->left,p,q);
        }
        if(p->val>root->val&&q->val>root->val){
            return lowestCommonAncestor(root->right,p,q);
        }
        return root;
    }
};

复杂度分析:

时间复杂度: O(N),N为二叉树的节点个数。如果这个二叉搜索树是满二叉树的话,每个节点都有左右孩子节点,那这个二叉搜索树就有logN层,如果每个节点都只有左孩子或者右孩子的话,那这个特殊的二叉树就和链表一样了,也就是有N层。
空间复杂度:当树有N层的时候,递归就有N次,需要开辟O(N)的 空间。

迭代方式

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        while(root){
            if(p->val<root->val&&q->val<root->val){
                root=root->left;
            }
            else if(p->val>root->val&&q->val>root->val){
                root=root->right;
            }
            else break;
        }
        return root;
        
    }
};

时间复杂度: O(N),N为二叉树的节点个数。如果这个二叉搜索树是满二叉树的话,每个节点都有左右孩子节点,那这个二叉搜索树就有logN层,如果每个节点都只有左孩子或者右孩子的话,那这个特殊的二叉树就和链表一样了,也就是有N层。
空间复杂度:O(1)。不用存储节点。

参考:leetcode

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值