代码随想录算法训练营第十八天| 找树左下角的值 、 路径总和 从中序与后序遍历序列构造二叉树

找树左下角的值  

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

1.层次遍历

1.1分析及思路

层次遍历的实现思路请看:102二叉树的层次遍历、107.二叉树的层次遍历II

在层次遍历的过程中,每次取每一层的第一个元素,就可以取到最终结果。返回该结果即可。

1.2代码及注释

typedef struct TreeNode ElemType;  // 定义结构体 Node 为 ElemType
#define MaxSize 10001  // 定义队列的最大容量为 10001
 
typedef struct {
    ElemType* data[MaxSize];  // 存储元素的数组
    int size;  // 队列当前元素个数
    int front, rear;  // 队列头尾指针
} SqQueue;
 
void InitQueue(SqQueue* Queue) {
    Queue->rear = Queue->front = 0;  // 初始化队列头尾指针
    Queue->size = 0;  // 初始化队列元素个数为 0
}
 
bool isEmpty(SqQueue Queue) {
    if (Queue.rear == Queue.front)  // 判断队列是否为空
        return true;
    return false;
}
 
bool GetTopQueue(SqQueue Queue, ElemType** x) {
    if ((Queue.size == 0) || (Queue.rear == Queue.front))  // 判断队列是否为空或已满
    return false;
    
    *x = Queue.data[Queue.front];  // 获取队首元素
    return true;
}
 
bool EnQueue(SqQueue* Queue, ElemType* x) {
    if ((Queue->rear + 1) % MaxSize == Queue->front)  // 判断队列是否已满
        return false;
    
    Queue->data[Queue->rear] = x;  // 入队
    Queue->rear = (Queue->rear + 1) % MaxSize;  // 更新 rear 指针
    Queue->size++;  // 更新队列元素个数
    return true;
}
 
bool DeQueue(SqQueue* Queue, ElemType** data) {
    if ((Queue->size == 0) || (Queue->rear == Queue->front))  // 判断队列是否为空
        return false;
    
    *data = (*Queue).data[Queue->front];  // 出队
    Queue->front = (Queue->front + 1) % MaxSize;  // 更新 front 指针
    Queue->size--;  // 更新队列元素个数
    return true;
}

int findBottomLeftValue(struct TreeNode* root) {
    int result;//记录返回结果
    SqQueue Queue;//定义一个队列
    InitQueue(&Queue);//初始化队列
    EnQueue(&Queue,root);//根节点入队列
    //队列为空时结束循环
    while(!isEmpty(Queue)){
        int QueueLen = Queue.size;//获取每一层的元素个数
        struct TreeNode* Node = NULL;//暂存出队列的结点
        for(int i=0;i<QueueLen;i++){
            DeQueue(&Queue,&Node);//元素出队列
            if(i == 0)//如果是每一层的第一个结点
                result = Node->val;//记录该结点的值,到结束循环时,刚好是最后一层的第一个结点的值
            if(Node->left != NULL)//左孩子不为空就入队列
                EnQueue(&Queue,Node->left);
            if(Node->right != NULL)//右孩子不为空就入队列
                EnQueue(&Queue,Node->right);
        }
    }
    return result;
}

2.递归

2.1分析及思路

叶子结点,才可能是最终的结果,什么是最总结果那,最深的那个叶子结点才是。所以,在取最终结果时,判断叶子结点的深度,若深则取。因为要访问整个树,所以转换为递归,判断每一个结点是不是叶子结点,以及判断深度是否大于最大值。

递归三部曲:

1.递归传递参数,递归返回值,递归函数的功能

递归函数的功能:判断是不是叶子结点,以及最深叶子的值

返回值:无,因为递归的下一层不需要为上一层做什么。

传入的参数:要进行深度的判断,所以要有当前深度,与最深的深度。需要一个最终结果所以有一个int型result,还需要一个结点

2.终止条件

因为不会传入NULL所以终止条件没有NULL。当传入普通结点时,要进行左右孩子的递归。当传入叶子结点时,因为左右孩子均没有直接返回。所以一个终止条件就是叶子结点结束,结束前处理数据。

3.单层递归逻辑

判断它是否是叶子结点,以及传入其左右孩子。即递归调用

2.2代码及注释

void traversal(struct TreeNode* root,int depth,int* maxDepth,int* result){
    // 如果当前节点是叶子节点
    if(root->left == NULL && root->right == NULL){
        // 如果当前节点深度大于最大深度,则更新最大深度和最深叶子节点的值
        if(depth > *maxDepth){
            *maxDepth = depth;
            *result = root->val;
        }
        return; // 返回
    }
    // 递归遍历左子树
    if(root->left != NULL){
        depth++; // 深度加1
        traversal(root->left,depth,maxDepth,result);
        depth--; // 深度减1,回溯
    }
    // 递归遍历右子树
    if(root->right != NULL){
        depth++; // 深度加1
        traversal(root->right,depth,maxDepth,result);
        depth--; // 深度减1,回溯
    }
}

int findBottomLeftValue(struct TreeNode* root) {
    int maxDepth = -1; // 初始化最大深度为-1
    int result = -1; // 初始化最深叶子节点的值为-1
    traversal(root,0,&maxDepth,&result); // 调用递归函数遍历二叉树
    return result; // 返回最深叶子节点的值
}

 路径总和

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

1.分析及思路

257. 二叉树的所有路径 (优先掌握递归)相似,只是把输出路径,改成了输出求和。

即遇到叶子结点我们就求和,判断是否与目标值相等。因为要遍历所有结点,所以使用递归。

递归三部曲:

1.确定返回值,传入的参数,函数的功能

函数的功能:判断结点是否有路径之和等于目标值。

传入的参数:存放路径的栈,传入的结点,目标值

返回值:有路径符合true,没有路径符合false。因为下一层遍历需要立即向上一层汇报其最终结果。所以需要返回值

2.终止条件

遇到叶子结点就判断加终止,因为树中不会存在NULL,在递归中也不会传入NULL。所以只有叶子结点是终止条件。

3.单层递归逻辑

寻找路径之和,即递归的调用函数。转路径时,就需要回溯。

2.代码及注释

#define MaxSize 50            //定义栈中元素的最大个数
typedef int ElemType;
typedef struct{
    ElemType data[MaxSize];   //存放栈中的元素
    int top;                  //栈顶指针
}SqStack;
/***************************************************************
** 功  能:初始化顺序栈
** 参  数:Stack 顺序栈的地址
** 返回值:无
****************************************************************/
void InitStack(SqStack *Stack){
    Stack->top = -1;//初始化栈顶指针
}
/***************************************************************
** 功  能:顺序栈的入栈
** 参  数:Stack顺序栈的地址,data传入的数据
** 返回值:true入栈成功  false入栈失败
****************************************************************/
bool Push(SqStack *Stack,ElemType data){
    if(Stack->top == MaxSize-1)      //判断栈是否满,若满则无法入栈
        return false;
    Stack->data[++Stack->top] = data;//栈顶指针先上移1,然后再入栈
    return true;
}
/***************************************************************
** 功  能:顺序栈的出栈
** 参  数:Stack顺序栈的地址,data保存数据的地址
** 返回值:true出栈成功  false出栈失败
****************************************************************/
bool Pop(SqStack *Stack,ElemType *data){
    if(Stack->top == -1)              //判断栈是否为空,为空无法出栈
        return false;
    if(data != NULL)
        *data = Stack->data[Stack->top];//不为空先赋值,然后指针再向下移动
    Stack->top--;
    return true;
}
/***************************************************************
** 功  能:顺序栈的获取栈顶元素
** 参  数:Stack顺序栈,data保存数据的地址
** 返回值:true获取成功  false获取失败
****************************************************************/
bool GetTop(SqStack Stack,ElemType *data){
    if(Stack.top == -1)           //判断栈是否为空,为空没有栈顶元素
        return false;
    *data = Stack.data[Stack.top];//读取栈顶元素
    return true;
}

bool traversal(struct TreeNode* root,int targetSum, SqStack* sta) {
    Push(sta,root->val); // 将当前节点的值压入栈中

    if (root->left == NULL && root->right == NULL) { // 如果当前节点是叶子节点
        int result = 0;//路径求和值
        for(int i=0;i<=sta->top;i++){
            result = result + sta->data[i];//把路径值加起来
        }
        if(result == targetSum)//判断是否符合条件
            return true;
        return false;
    }
    if(root->left){ // 如果左子树不为空
        if( traversal(root->left,targetSum, sta) ) return true; // 递归处理左子树
        Pop(sta,NULL); //回溯,处理完一个叶子结点后会返回到这里,需要把叶子结点出栈,寻找下一条路径
    }
    if(root->right){ // 如果右子树不为空
        if( traversal(root->right,targetSum, sta) ) return true; // 递归处理右子树
        Pop(sta,NULL); //回溯
    }
    return false;
}

bool hasPathSum(struct TreeNode* root, int targetSum) {
    if(root == NULL)
        return false;
    SqStack Stack;//定义一个栈
    InitStack(&Stack);//初始化栈
    return traversal(root,targetSum,&Stack);
}

卡哥思路

使用递减的方式,求解。

1.返回值,参数,功能

返回值:每个结点,都应立即向上返回,判断的结果

参数:结点,目标数

2.终止条件

碰到叶子结点就处理,即

    //遇到叶子结点,且targetSum等于0
    if(root->left == NULL && root->right == NULL && targetSum == 0) return true;
    //不等于零则不符合返回false
    if(root->left == NULL && root->right == NULL) return false;

3.单层递归逻辑

更新新结点。加回溯。

bool traversal(struct TreeNode* root,int targetSum) {
    //遇到叶子结点,且targetSum等于0
    if(root->left == NULL && root->right == NULL && targetSum == 0) return true;
    //不等于零则不符合返回false
    if(root->left == NULL && root->right == NULL) return false;
    //向左子树遍历
    if(root->left != NULL){
        //目标值减去结点值,即处理该结点
        targetSum = targetSum - root->left->val;
        //判断左子树有没有符合条件
        if( traversal(root->left,targetSum) ) return true;
        //回溯
        targetSum = targetSum + root->left->val;
    }
    if(root->right != NULL){
        //处理该结点
        targetSum = targetSum - root->right->val;
        //查看右子树
        if( traversal(root->right,targetSum) ) return true;
        targetSum = targetSum + root->right->val;//回溯
    }
    return false;
}

bool hasPathSum(struct TreeNode* root, int targetSum) {
    if(root == NULL) return false;
    return traversal(root,targetSum - root->val);
}

从中序与后序遍历序列构造二叉树 

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

1.分析及思路

很明显使用递归。

递归三部曲:

1.确定函数功能,返回值,传入的参数

函数功能: 为结点设置左右孩子,并且返回它

返回值:上一层需要下一层的根节点,所以需要返回值为struct TreeNode*类型

传入的参数:一定有前序和后序遍历的数组,有数组就有它们的数组大小

2.终止条件

遇到空结点时返回。

3.确定单层递归

拿到一个数之后,就为其分配空间,然后找它的左右孩子。

找左右孩子就是,进行递归的调用,即划数组。

2.代码及注释

struct TreeNode* buildTree(int* inorder, int inorderSize, int* postorder, int postorderSize) {
    //递归出口当后序遍历的数组为空时,则说明遍历结束
    if(postorderSize == 0) return NULL;
    //每次取后序遍历的最后一个元素最为根节点
    int postLast = postorder[postorderSize - 1];
    //为结点申请空间
    struct TreeNode* Node = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    //为结点输入值,并且初始化其孩子指针
    Node->val = postLast;
    Node->left = NULL;
    Node->right = NULL;
    //当后续遍历数组剩余1时,它一定是叶子结点,直接返回即可
    if(postorderSize == 1) return Node;//不加也可以,就是最后一个结点没必要进行下面的操作了
    int index;//确定根节点的下标,以便分割
    for(index=0;index<postorderSize;index++)
        if(inorder[index] == postLast) break;
    //左子树寻找根节点
    Node->left = buildTree(inorder,index,postorder,index);
    //右子树寻找根节点
    Node->right = buildTree(&inorder[index+1],postorderSize-index-1,&postorder[index],postorderSize-index-1);
    return Node;
}

3.部分代码的讲解

使用左闭右开原则。

    for(index=0;index<postorderSize;index++)
        if(inorder[index] == postLast) break;

寻找根节点的下标,因为根节点的左边全部是左子树,右边全部是右子树。

inorder[0] 到 inorder[index]是左子树
inorder[index+1] 到 inorder[inorderSize]是右子树
这里是左闭右开

左子树大小为index。右子树大小为postorderSize-index-1,即总结点数减去左子树,减去根节点。

总结:递归的返回值的设计

需要返回值的情况:
1. 递归函数需要计算出一个最终结果,如斐波那契数列、阶乘等数学计算问题。
2. 递归函数需要返回某种状态信息,如判断一个树是否是平衡树、路径总和。
3. 递归函数需要返回一个中间结果,用于后续计算,如在动态规划中递归计算子问题的结果。

不需要返回值的情况:
1. 递归函数只需要修改传入的参数或进行某些操作,不需要返回任何计算结果,如树的遍历、图的遍历等问题。
2. 递归函数只需要判断某种条件是否成立,不需要返回具体的结果,如判断一个字符串是否是回文串等问题。
3. 递归函数只需要执行某些操作,不需要返回任何信息,如在回溯算法中的搜索过程。

总的来说,需要返回值的情况通常涉及到计算结果或状态信息的传递,而不需要返回值的情况通常涉及到操作或判断的执行。

如有错误还请指正,若有疑问欢迎留言讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值