LeetCode算法题解——二叉树的递归求解

本文深入探讨二叉树的递归求解方法,包括最大深度、平衡性判断、路径总和、翻转、合并、直径计算、路径计数及子树识别等问题,通过实例解析递归边界和非边界条件的处理技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


因为树天生是一种递归结构,所以很多树的问题可以使用递归来进行处理。递归算法的特点,是子问题和原问题具有相同的解结构,并且原问题的解依赖于子问题的解。在原问题中递归求解子问题,是递归算法求解的精髓。
递归算法主要关注两个问题:第一是如果到达递归边界怎么办;第二是如果没有到达递归边界怎么办。在思考的时候,按照正常的思路,我们应该是先思考没有到达边界的情况,然后再思考到达边界的情况;在写代码的时候,我们应该先写到达递归边界怎么办,然后再写没有到达递归边界怎么办。

直接递归

104. 二叉树的最大深度

思路
首先,二叉树的深度,为根节点到最远叶子节点的最长路径上的节点数。
我们先考虑到达递归边界怎么办。如果到达了树的叶节点的左右节点,此时的root为空节点,空节点的最大深度为0,那么返回0;
然后考虑没有到达递归边界怎么办。如果到达了树的叶节点及之前,对于每一个到达的节点,以该节点为root的子树的最大深度,为该节点左右节点的最大深度+1。这里的+1,就是加上该节点。
回到原问题,对于根节点来说,根节点的最大深度=max(根结点左叶节点的最大深度,根结点右叶节点的最大深度)+1。这里的+1,就是加上根节点。
代码

/**
 * 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:
    int maxDepth(TreeNode* root)
     {
        if(root == NULL) return 0;
        return max(maxDepth(root->left), maxDepth(root->right)) + 1;
    }
};

110. 平衡二叉树

思路
递归,就是在函数中调用自身。如果发现没有在函数中调用自身,那就是没有进行递归。
这题带给我最直接的启发,就是不只在原问题上操作,还要记得递归地在子问题上操作。
代码

/**
 * 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:
    int maxDepth(TreeNode* root){
        if(root == NULL) return 0;
        return max(maxDepth(root->left), maxDepth(root->right)) + 1;
    }
    
public:
    bool isBalanced(TreeNode* root) {
        if(root == NULL) return true;
        if(abs(maxDepth(root->left) - maxDepth(root->right)) <= 1 && isBalanced(root->left) && isBalanced(root->right)) return true;
        return false;
    }
};

112. 路径总和

思路
这题带给我最直接的启发,就是考虑到空节点怎么办、到叶节点怎么办、到普通节点怎么办。
代码

/**
 * 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:
    bool hasPathSum(TreeNode* root, int sum) 
    {
    	// 到达空节点,肯定不满足要求,返回false
        if(root == NULL) return false;
        // 到达叶节点,看是否满足要求
        if(root->left == NULL && root->right == NULL && root->val == sum) return true;
        // 达到普通节点,对原问题操作,对子问题递归操作
        return hasPathSum(root->left, sum - root->val) || hasPathSum(root->right, sum - root->val);
    }
};

226. 翻转二叉树

思路
当遍历到一个节点时,不只是该节点的左右子树要交换,左右子树也要递归地交换。
我们先考虑到达递归边界怎么办。如果到达了树的叶节点的左右节点,显然叶节点的左右节点都为空,不用翻转,直接返回NULL。
然后考虑没有到达递归边界怎么办。如果到达了树的叶节点及之前,对于每一个到达的节点,翻转该节点的左右节点。注意,这里的翻转该节点的左右节点,是指递归地翻转。这是我们第一次注意到子问题的递归。也就是说,不能简单地直接交换该节点的左右节点,而是在对两个左右节点进行翻转后,再交换。
代码

/**
 * 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* invertTree(TreeNode* root) {
        if(root == NULL) return NULL;
        // 注意,你在交换的时候,就像交换两个数一样,要用一个变量作为中间容器
        TreeNode* temp = root->left;
        root->left = invertTree(root->right);
        root->right = invertTree(temp);
        
        return root;
    }
};

617. 合并二叉树

思路
和之前不同,这次我们拿到了两棵树。
我们先考虑到达递归边界怎么办。不妨假设第一棵树,先到达了树的叶节点的左节点t1,那么第二棵树有两种可能:t2到达了树的叶节点的左节点,或者,还没有到达树的叶节点的左节点。对于第一种情况,两棵树都到达了左节点,合并后显然节点仍为空,返回NULL。对于第二种情况,一棵树都到达了空节点,另一棵树没有到达空节点,合并后显然为非空节点,返回非空节点。
然后考虑没有到达递归边界怎么办。那么两棵树都没有达到非空节点,那么我们先对t1和t2进行合并,新节点t3的值为t1->val + t2->val。然后,注意,递归地对t1,t2的左右节点进行合并,然后将新的左右节点赋给t3。这是我们第二次注意到子问题的递归。你会发现,在原问题中递归求解子问题,是递归算法求解的精髓。问题在于,如何在恰当的时候求解子问题。
代码

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        if(t1 == NULL && t2 == NULL) return NULL;
        if(t1 == NULL) return t2;
        if(t2 == NULL) return t1;
        
        TreeNode* root = new TreeNode(t1->val + t2->val);
        root->left = mergeTrees(t1->left, t2->left);
        root->right = mergeTrees(t1->right, t2->right);
        
        return root;
    }
};

子函数中递归

543. 二叉树的直径

思路
对于每一个节点,都去求其左右子树的最大深度。这道题与单纯求树的最大深度不同点在于,在求左右子树的深度时,需要更新最大值。
我们先考虑到达递归边界怎么办。和之前的题目一样,如果到达了树的叶节点的左右节点,那么我们要求叶节点的左右节点的最大深度。显然,叶节点的左右节点都为空,空节点的最大深度为0,那么返回0。
然后考虑没有到达递归边界怎么办。如果到达了树的叶节点及之前,对于每一个到达的节点,该节点的最大深度,为该节点左右节点的最大深度+1。这里的+1,就是加上该节点。但是在返回结果之前,需要将已有的最大值max和该节点的左右子树深度之和进行比较,更新最大值。
代码

class Solution {
private:
    int res = 0;
    
public:
    int depth(TreeNode* root){
        if(root == NULL) return 0;
        int l = depth(root->left);
        int r = depth(root->right);
        res = max(res, l + r);
        return max(l, r) + 1;
    }
        
public:
    int diameterOfBinaryTree(TreeNode* root) {
        depth(root);
        return res;
    }
};

437. 路径总和 III

思路
在这里,我们直接以每个节点为根节点,计算路径和为sum的有几条,然后加起来。
我们先考虑到达递归边界怎么办。如果到达了树的叶节点的左右节点,左右节点都是空节点,没有值,自然无法求和,返回0。
然后考虑没有到达递归边界怎么办。如果到达了树的叶节点及之前,对于每一个到达的节点,以该节点为根节点,求解路径和为sum的数量。并且递归地求解左右子树中,路径和为sum的数量。注意,这里根节点和左右子树调用的函数是不同的。根节点调用的函数,是以根节点的值为起始值进行求和;左右子树调用的函数,包含了不以左右子树开始计算的部分。
代码

class Solution {
public:
    int pathSumRoot(TreeNode* root, int sum){
        if(root == NULL) return 0;
        int res = 0;
        if(root->val == sum) res++;
        res += pathSumRoot(root->left, sum - root->val) + pathSumRoot(root->right, sum - root->val);
        return res;
    } 
    
public:
    int pathSum(TreeNode* root, int sum) {
        if(root == NULL) return 0;
        int res = pathSumRoot(root, sum) + pathSum(root->left, sum) + pathSum(root->right, sum);
        return res;
    }
};

572. 另一个树的子树

思路
和之前不同,这次我们拿到了两棵树s和t。
我们求解t是不是s的子树,那么有三种可能:t就等于s本身,或t是s的左子树的子树,或t是s的右子树的子树。这说明子树的问题可以递归求解,那我们就有了第一个递归函数。但是如何判断两个树是否相等呢?
两棵树相等,要满足三个条件:根节点值相等,且s的左子树和t的左子树相等,且s的右子树和t的右子树相等。同样的,判断两棵树相等也可以通过递归解决。
和之前的题目不同,这道题带给我的思考,是我们不能一上来就写递归条件,我们需要先分析递归的问题是什么。
代码

class Solution {
public:
    bool isSubtreeWithRoot(TreeNode* s, TreeNode* t){
        if(s == NULL && t == NULL) return true;
        if(s == NULL || t == NULL) return false;
        return s->val == t->val && isSubtreeWithRoot(s->left, t->left) && isSubtreeWithRoot(s->right, t->right);
    }
    
public:
    bool isSubtree(TreeNode* s, TreeNode* t) {
        if(s == NULL) return false;
        return isSubtreeWithRoot(s, t) || isSubtree(s->left, t) || isSubtree(s->right, t);
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值