二叉树力扣刷题(递归法)

概述

递归法是二叉树算法题中一种很常见的解法,由于二叉树的特殊结构,用递归法解题时往往会比迭代法省时省力很多。

先来明确一下递归通用的三步法:

  1. 确定递归函数的参数和返回值 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。

  2. 确定终止条件 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。

  3. 确定单层递归的逻辑 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。

二叉树的前中后序都可以用递归法实现,这里不再赘述,我们来看看递归法在实际解题中的应用 

例题

一、 104. 二叉树的最大深度

为什么想到递归

求最大深度,我们首先想到将次节点的左子树深度与右子树深度比较,取其较大值再+1(+1表示该节点本身),就是该节点的最大深度。从根节点向下不断遍历,再从叶子结点将深度不断向上返回,就可以用递归实现。

如何实现

1、确定递归函数的参数和返回值

int maxDepth(struct TreeNode* root)

2、确定终止条件

if (root == NULL) {
        return 0;
    }

3、确定单层递归的逻辑

    int leftDepth = maxDepth(root->left);
    int rightDepth = maxDepth(root->right);

    return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;

总体代码

int maxDepth(struct TreeNode* root) {
    if (root == NULL) {
        return 0;
    }

    int leftDepth = maxDepth(root->left);//求左子树最大深度
    int rightDepth = maxDepth(root->right);//求右子树最大深度

    return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}

二、111. 二叉树的最小深度

此题乍一看只需将上一题代码返回值改为返回较小值+1即可,但这却忽略了一种情况

即:二叉树根节点只有左子树或右子树 ,这样在上述代码下leftDepth(或rightDepth)为0,所以最终函数会直接返回1(只算根节点),而不去计算叶子结点。

为了避免这种情况,我们在递归中加入以下逻辑:

如果左子树为空,则去计算右子树最小深度

如果右子树为空,则去计算左子树最小深度

代码如下:

int minDepth(struct TreeNode* root) {
    if (root == NULL) {
        return 0;
    }
    //如果左右子树其中一个为空,直接去搜寻另一个不为空的子树
    if (root->left == NULL) {
        int depth = minDepth(root->right);
        return depth + 1;
    }
    if (root->right == NULL) {
        int depth = minDepth(root->left);
        return depth + 1;
    }

    //如果左右子树都不为空
    int leftdepth = minDepth(root->left);
    int rightdepth = minDepth(root->right);
    return leftdepth > rightdepth ? rightdepth + 1 : leftdepth + 1;//返回其高度最小值
}

 三、110. 平衡二叉树

此题需在上一题的基础上实现,即通过不断地递归,求每个节点的左右子树之差先通过上题代码求左右子树高度),判断其符不符合平衡二叉树要求

总结:从上至下遍历,先判断父节点是否平衡,在递归分别判断左子树和右子树是否平衡

 

int maxDepth(struct TreeNode* root) {//求树的深度
    if (root == NULL) {
        return 0;
    }

    int leftDepth = maxDepth(root->left);
    int rightDepth = maxDepth(root->right);

    return leftDepth > rightDepth ? leftDepth+1 : rightDepth+1;
}

bool isBalanced(struct TreeNode* root) {
    if (root == NULL) {
        return true;
    }

    int leftDepth = maxDepth(root->left);//不断递归求左子树与右子树的深度
    int rightDepth = maxDepth(root->right);

    return abs(leftDepth - rightDepth) < 2 //先判断此节点是否平衡
        && isBalanced(root->left) //不断返回子节点是否平衡
        && isBalanced(root->right);
}

四、101. 对称二叉树

总体思路

要判断一颗树是否总体对称,先判断根节点的左子树a和右子树b是否相等(如果相等,则将其视为对称的左右子树ab

再判断  1、a的左子树和b的右子树是否相等  2、a的右子树和b的左子树是否相等

如果情况1、2都成立,则说明a、b的子树都对称

随后递归将1中a的左子树和b的右子树视为新的对称的左右子树ab,以相同的方法判断其子树是否对称(同时符合1、2两种情况),2中a的右子树和b的左子树视为新的对称的左右子树ab,以相同方法判断

重点(边界判断)

1、如果左右节点都为空,则对称

2、如果左右节点只有一个为空,则不对称

3、左右节点都不为空的情况下,如果值不相等则不对称,如果相等则继续判断其子树是否对称

代码实现

bool compare(struct TreeNode* left, struct TreeNode* right){//判断left和right是否对称
    // 首先排除空节点的情况
    if (left == NULL && right != NULL)
        return false;
    else if (left != NULL && right == NULL)
        return false;
    else if (left == NULL && right == NULL)
        return true;
    // 排除了空节点,再排除数值不相同的情况
    else if (left->val != right->val)
        return false;

    // 此时就是:左右节点都不为空,且数值相同的情况
    // 此时才做递归,做下一层的判断
    bool outside = compare(left->left, right->right); //判断外侧子树是否对称
    bool inside = compare(left->right, right->left); //判断内侧子树是否对称
    bool isSame = outside && inside; 
    return isSame;
}

bool isSymmetric(struct TreeNode* root) {
    if (root == NULL)
        return true;
    return compare(root->left, root->right);
}

五、257. 二叉树的所有路径

此题用到了深度优先搜索,不断搜寻当前节点的左子树和右子树的所有路径,直到找到叶子节点,则代表一条路径的结束,将此路径记录 

//深度优先搜索
void construct_paths(struct TreeNode* root, char** paths, int* returnSize, int* sta, int top) {
//函数作用:递归地构造从根节点到叶子节点的所有路径  
//root: 当前遍历的节点  paths: 存储所有路径的数组  returnSize: 当前已经存储的路径数量  sta: 存储当前路径的栈  top: 栈顶索引  
    if (root != NULL) { // 如果当前节点不为空  
        if (root->left == NULL && root->right == NULL) { // 当前节点是叶子节点  
            char* tmp = (char*)malloc(1001); // 分配一个足够大的空间来存储路径  
            int len = 0; // 当前路径的长度  
  
            // 遍历栈,将栈中的节点值用 "->" 连接起来  
            for (int i = 0; i < top; i++) {  
                len += sprintf(tmp + len, "%d->", sta[i]);//sprintf返回值是写入的字符数  
            }  
  
            // 添加叶子节点的值到路径的末尾,并去掉最后一个 "->"  
            sprintf(tmp + len, "%d", root->val);  
  
            // 将路径添加到答案数组中  
            paths[(*returnSize)++] = tmp;  
        } 
        
        else { // 当前节点不是叶子节点  
            sta[top++] = root->val; // 将当前节点的值压入栈中  
  
            // 递归地构造左子树的所有路径  
            construct_paths(root->left, paths, returnSize, sta, top);  
  
            // 递归地构造右子树的所有路径  
            construct_paths(root->right, paths, returnSize, sta, top); 
        }  
    }  
}  
  
// 函数:binaryTreePaths  
// 功能:返回二叉树的所有路径  
// 参数:  
//   root: 二叉树的根节点  
//   returnSize: 存储返回的路径数量  
char** binaryTreePaths(struct TreeNode* root, int* returnSize) {  
    char** paths = (char**)malloc(sizeof(char*) * 1001); // 分配足够大的空间来存储所有路径的指针  
    *returnSize = 0; // 初始化已存储的路径数量  
    int sta[1001]; // 栈,用于存储当前路径的节点值  
  
    // 调用递归函数构造所有路径  
    construct_paths(root, paths, returnSize, sta, 0);  

    return paths; // 返回所有路径的数组  
}

 

  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,关于力扣刷题C++常用操作,我可以给你一些常见的操作和技巧: 1. 使用 STL 容器和算法库:STL(Standard Template Library)是 C++ 标准库中的一个重要组成部分,包含了许多常用的容器和算法。在力扣刷题中,使用 STL 可以大大提高代码的效率和可读性。例如,vector 可以用来存储动态数组,sort 可以用来排序等等。 2. 使用 auto 关键字:auto 关键字可以自动推导变量类型,可以减少代码量和提高可读性。例如,auto x = 1; 可以自动推导出 x 的类型为 int。 3. 使用 lambda 表达式:lambda 表达式是 C++11 中引入的一种匿名函数,可以方便地定义一些简单的函数对象。在力扣刷题中,使用 lambda 表达式可以简化代码,例如在 sort 函数中自定义比较函数。 4. 使用位运算:位运算是一种高效的运算方式,在力扣刷题中经常会用到。例如,左移运算符 << 可以用来计算 2 的幂次方,右移运算符 >> 可以用来除以 2 等等。 5. 使用递归递归是一种常见的算法思想,在力扣刷题中也经常会用到。例如,二叉树的遍历、链表的反转等等。 6. 使用 STL 中的 priority_queue:priority_queue 是 STL 中的一个容器,可以用来实现堆。在力扣刷题中,使用 priority_queue 可以方便地实现一些需要维护最大值或最小值的算法。 7. 使用 STL 中的 unordered_map:unordered_map 是 STL 中的一个容器,可以用来实现哈希表。在力扣刷题中,使用 unordered_map 可以方便地实现一些需要快速查找和插入的算法。 8. 使用 STL 中的 string:string 是 STL 中的一个容器,可以用来存储字符串。在力扣刷题中,使用 string 可以方便地处理字符串相关的问。 9. 注意边界条件:在力扣刷题中,边界条件往往是解决问的关键。需要仔细分析目,考虑各种边界情况,避免出现错误。 10. 注意时间复杂度:在力扣刷题中,时间复杂度往往是评判代码优劣的重要指标。需要仔细分析算法的时间复杂度,并尽可能优化代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值