代码随想录算法训练营第二十一天|530.二叉搜索树的最小绝对差、 501.二叉搜索树中的众数 、236. 二叉树的最近公共祖先

文章详细讲解了如何在二叉搜索树中通过递归和双指针技巧找到最小绝对差值,以及如何利用中序遍历和自底向上查找来找到众数和最近公共祖先。
摘要由CSDN通过智能技术生成

530.二叉搜索树的最小绝对差 

题目链接/文章讲解: 代码随想录

1.方法1

1.1分析及思路

了解到差值最小的数为,结点左孩子然后一直向右的叶子结点,或者是右孩子一直向左的叶子结点。

对每一个结点都进行这样的判断。所以就用到了递归。

递归三部曲:

1.功能,参数,返回值

功能:判断结点的最小差值

参数:保存最小值的int*地址,以及结点

返回值:没有必要向上层返回值,所以不需要返回值

2.递归终止条件

假设传入一个非空结点,要进行的操作就是,寻找其前后最接近的值。所以只有NULL是才会结束循环。

3.单层递归逻辑

拿到一个结点,要进行寻找其左孩子的最右结点,以及右孩子的最左结点,然后求最小差值。

1.2代码及注释

void LETF(struct TreeNode* root,int* Min) {
    // 定义一个指向左子树的节点Node
    struct TreeNode* Node = root->left;
    // 遍历左子树
    while(Node!=NULL){
        // 如果Node的右子节点为空,则说明找到了,左孩子的最右侧的叶子结点
        if(Node->right == NULL)
            // 更新最小值Min
            if(*Min > root->val - Node->val)
                *Min = root->val - Node->val;
        Node = Node->right;
    }
}

void RIGHT(struct TreeNode* root,int* Min) {
    // 定义一个指向右子树的节点Node
    struct TreeNode* Node = root->right;
    // 遍历右子树
    while(Node!=NULL){
        // 如果Node的左子节点为空,则说明找到了右孩子的最左侧叶子结点
        if(Node->left == NULL)
            // 更新最小值Min
            if(*Min > Node->val - root->val)
                *Min = Node->val - root->val;
        Node = Node->left;
    }
}

void traversal(struct TreeNode* root,int* Min) {
    // 如果当前节点为空,直接返回
    if(root == NULL) return;
    if(root->left != NULL)
        LETF(root,Min);//寻找左孩子的最右侧叶子结点
    if(root->right != NULL)
        RIGHT(root,Min);//寻找右孩子的最左侧结点
    // 递归遍历左子树
    traversal(root->left,Min);
    // 递归遍历右子树
    traversal(root->right,Min);
}

int getMinimumDifference(struct TreeNode* root) {
    // 初始化最小值为INT_MAX
    int Min = INT_MAX;
    // 从根节点开始遍历
    traversal(root,&Min);
    // 返回最小值
    return Min;
}

2.方法2

2.1分析及思路

98.验证二叉搜索树思路类似,用两个指针进行遍历,这俩个指针的差值是可能成为最小差值的,因为中序遍历是会得到一个递增的数组的。

1.功能、参数、返回值

功能:得到最小的差值

参数:保存最小差值的Min、根节点root、访问过的前一个结点pre

返回值:下一侧递归不需要及时返回信息,所以不需要返回值

2.终止条件

当访问到NULL时说明该树已经访问完毕。所以终止条件为NULL

3.单层递归的逻辑

拿到一个结点root,用root-pre判断差值,然后递归的访问其左子树,右子树

2.2代码及分析

// 定义一个函数用于遍历二叉搜索树
void traversal(struct TreeNode* root, struct TreeNode* *pre, int* Min) {
    // 如果当前节点为空,直接返回
    if(root == NULL) return;
    
    // 递归遍历左子树
    traversal(root->left, pre, Min);
    
    // 如果前一个节点不为空
    if(*pre != NULL){
        // 更新最小差值
        if(*Min > (root->val - (*pre)->val)){
            *Min = (root->val - (*pre)->val);
        }
    }
    
    // 更新前一个节点为当前节点
    *pre = root;
    
    // 递归遍历右子树
    traversal(root->right, pre, Min);
}

// 定义一个函数用于获取二叉搜索树中节点值的最小差值
int getMinimumDifference(struct TreeNode* root) {
    // 初始化最小差值为最大整数值
    int Min = INT_MAX;
    
    // 初始化前一个节点为空
    struct TreeNode* pre = NULL;
    
    // 调用遍历函数
    traversal(root, &pre, &Min);
    
    // 返回最小差值
    return Min;
}

501.二叉搜索树中的众数 

1.分析及思路

最直观的想法就是,访问一遍树,把所有的数出现的频率都统计下来,然后再把频率高的数进行返回。

进行优化,对于二叉搜索树而言,中序遍历会得到一个不减的数组,和上一题类似,使用双指针进行频率的统计,如果当前频率等于最大频率,则说明众数有多个,在结果数组的末尾加上它即可。若大于则说明,已经统计的结果全部作废,要重新统计,即再次从数组下标为0的地方存储结果。

运用了中序遍历,所以可以使用递归法,递归三部曲:

1.功能,参数,返回值

功能:统计二叉树中的众数

参数:需要传入一个根节点,所以有struct TreeNode* root

因为是双指针,所以需要一个struct TreeNode** pre,保存上次访问过的结点

最终返回的是一个数组,所以穿进来一个int* result

以及统计数组的大小的int* returnSize

需要当前频率和最大频率做对比,所以有int* MaxCount,int* NowCount。

2.递归终止条件

很明显,遇到空结点时,没办法统计所以终止。

3.单层递归逻辑

遇到一个结点,看上次访问的结点与它是否相等,相等则频率加1。若不想等则说明,此时访问的是新出现的,此时频率设置为1。若该频率与最大频率相等,则把它加入结果数组的末尾。若大于,则说明,结果数组中的众数全部作废,重新统计。

2.代码及注释

void traversal(struct TreeNode* root, struct TreeNode** pre, int* returnSize, int* result, int* MaxCount, int* NowCount) {
    // 如果当前节点为空,直接返回
    if(root == NULL) return;
    
    // 递归遍历左子树
    traversal(root->left, pre, returnSize, result, MaxCount, NowCount);
    
    // 如果前一个节点为空,则当前节点是第一个节点,NowCount置为1
    if(*pre == NULL) *NowCount = 1;
    // 如果前一个节点的值等于当前节点的值,则当前节点是重复节点,NowCount加1
    else if((*pre)->val == root->val) (*NowCount)++;
    // 如果前一个节点的值不等于当前节点的值,则当前节点是新节点,NowCount置为1
    else *NowCount = 1;
    
    // 更新pre指针为当前节点
    *pre = root;
    
    // 如果NowCount等于MaxCount,将当前节点的值加入结果数组并增加返回大小
    if(*NowCount == *MaxCount) result[(*returnSize)++] = root->val;
    // 如果NowCount大于MaxCount,更新MaxCount为NowCount,重置返回大小为1,将当前节点的值放入结果数组
    else if(*NowCount > *MaxCount){
        *MaxCount = *NowCount;
        *returnSize = 1;
        result[0] = root->val;
    }
    
    // 递归遍历右子树
    traversal(root->right, pre, returnSize, result, MaxCount, NowCount);
}

int* findMode(struct TreeNode* root, int* returnSize) {
    // 初始化返回大小为0
    *returnSize = 0;
    
    // 分配结果数组的内存空间
    int* result = (int*)malloc(sizeof(int)*10001);
    
    // 初始化前一个节点指针、最大出现次数、当前出现次数为0
    struct TreeNode* pre = NULL;
    int MaxCount = 0;
    int NowCount = 0;
    
    // 调用traversal函数进行遍历
    traversal(root, &pre, returnSize, result, &MaxCount, &NowCount);
    
    // 返回结果数组
    return result;
}

236. 二叉树的最近公共祖先  

1.分析及思路

很明显,要想知道最近公共祖先结点,就要从该结点左右子树中找到qp两个结点。也就是先访问其左子树,再访问其右子树,也就是使用的是后续遍历。即找到结点后就向父结点返回。

递归三部曲:

1.功能、参数、返回值

功能:返回最近公共祖先

参数:根节点、p、q

返回值:因为需要向父结点返回有没有该结点,所以需要返回值

2.递归终止条件

当传入NULL时,很明显终止。当找到该结点时,也应该终止。

3.单层递归逻辑

遇到结点时,先访问其左子树,在访问其右子树,才能得到该结点是不是有,目标结点。

1.若左子树有目标结点,右子树也有目标结点,则说明它就是最近公共祖先。

2.若右子树有,左子树没有,则说明最近公共祖先在右侧,即返回右侧返回的结点

3.左子树有,右子树没有,则返回左子树返回的值

4.若都没有很明显,返回NULL

2.代码及注释

// 定义一个函数,用于找到两个节点的最近公共祖先
struct TreeNode* lowestCommonAncestor(struct TreeNode* root, struct TreeNode* p, struct TreeNode* q) {
    // 如果根节点为空,返回空
    if(root == NULL) return root;
    // 如果根节点等于其中一个节点,直接返回根节点
    if(root == q || root == p) return root;
    // 递归查找左子树中的最近公共祖先
    struct TreeNode* left = lowestCommonAncestor(root->left,p,q);
    // 递归查找右子树中的最近公共祖先
    struct TreeNode* right = lowestCommonAncestor(root->right,p,q);
    // 如果左右子树都找到了最近公共祖先,则返回根节点
    if(left != NULL && right != NULL) return root;
    // 如果左子树找到了最近公共祖先,则返回左子树的最近公共祖先
    if(left == NULL && right != NULL) return right;
    // 如果右子树找到了最近公共祖先,则返回右子树的最近公共祖先
    if(left != NULL && right == NULL) return left;
    // 如果左右子树都没有找到最近公共祖先,则返回空
    return NULL;
}

  • 29
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值