【数据结构】Leetcode—— 树 递归经典题


1. 树的高度(104)

题目:
      给定一个二叉树,找出其最大深度。
      二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

思路:
      若使用递归版本,它的递归基应为当节点为空时,返回0;
      若使用迭代版本,借助队列对树进行层次遍历,最后返回层数即可。

class Solution {
public:
    int maxDepth(TreeNode* root) {
        /* 递归版本 两行搞定
        if(!root) return 0; // 递归基
        return max(maxDepth(root->left), maxDepth(root->right)) +1;
        */

        //迭代版本 层次遍历树,需引入队列
        queue<TreeNode*> que;
        int ans = 0;
        if(!root) return 0;
        que.push(root);
        while(!que.empty()){
            int s = que.size();
            ans++;
            for(int i=0; i<s; i++){
                TreeNode* temp = que.front();        
                que.pop();
                if(temp->left) que.push(temp->left);
                if(temp->right) que.push(temp->right);
            }
        }
        return ans;
    }
};

2. 二叉树的最小深度(111)

题目:
     给定一个二叉树,找出其最小深度。

     最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

思路:
     与树的高度(104)题类似,104题相当于返回二叉树的最大深度,通过将104题递归中的max改为min即可。
注意:
     这里应该加一个限制条件,若树为[[1], [2]]时,该树的最小深度应该是2而不是1。

class Solution {
public:
    int minDepth(TreeNode* root) {
        if(!root) return 0;
        if(!root->left || !root->right) return minDepth(root->left) + minDepth(root->right) + 1 ;
        return min(minDepth(root->left), minDepth(root->right)) +1;
    }
};

3. 平衡二叉树(110)

题目:
      给定一个二叉树,判断它是否是高度平衡的二叉树。

      本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。

思路:

  1. 对当前节点,分别求解左子树和右子树的深度,判断左右子树的高度差是否<=1。(利用了104题中求解二叉树的深度的方法)
  2. 然后再对当前节点的左节点和右节点做同样操作。
class Solution {
public:
    bool isBalanced(TreeNode* root) {
        if(!root) return true;
        int d = abs(depth(root->left) - depth(root->right));
        return (d<=1)&&isBalanced(root->left)&&isBalanced(root->right);
    }
    int depth(TreeNode* a){
        if(!a) return 0;
        return max(depth(a->left), depth(a->right))+1;
    }
};

4. 树中两节点的最长路径长度(543)

题目:
      给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

示例:
      Input:

     1
    / \
   2   3
  / \
 4   5

      Output:3

思路:
      首先通过上面的两题已经熟悉了如何得到树的深度,即返回根节点左子树和右子树深度的最大值。本题中可以将根节点左子树和右子树的深度相加,并不断通过length = max(length, ld+rd);来保证length中存储的为深度求和的最大值,即可得到树中两节点的最长路径长度。

class Solution {
public:
    int length;
    int diameterOfBinaryTree(TreeNode* root) {
        if(!root) return 0;
        depth(root);
        return length;
    }
    int depth(TreeNode* node){
        if(!node) return 0;
        int ld = depth(node->left);
        int rd = depth(node->right);
        length = max(length, ld+rd);
        return max(ld, rd) +1;
    }
};

5. 翻转二叉树(226)

示例:

思路:

递归三部曲(每次使用递归考虑的三个点):

  1. 确定递归函数的参数和返回值
    这里的参数是要传入节点的指针,不需要其他参数了,返回值的话其实也不需要,但是题目中需要返回root节点的指针,可以直接使用题目定义好的函数;

  2. 确定递归基
    当节点为空时,返回树的根节点;

  3. 确定单层递归的逻辑
    对于每个节点,先交换其左右节点,然后分别对该节点的左右子节点翻转即可。

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if(root == NULL) return root;
        swap(root->left, root->right);
        invertTree(root->left);
        invertTree(root->right);
        return root;
    }
};

6. 合并二叉树(617)

题目:
     给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。

     你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

示例:

思路:

递归三部曲(每次使用递归考虑的三个点):

  1. 确定递归函数的参数和返回值
    传入两个待合并树节点的指针,返回合并后树呃根节点,可以直接使用题目定义好的函数;

  2. 确定终止条件
    当两个节点同时为NULL时,返回NULL,当两节点只有其中一个节点为NULL时,返回另一个不为NULL的节点指针;

  3. 确定单层递归的逻辑
    对于输入两个树的相同位置的节点,new一个新节点,该新节点的值为(t1->val + t2->val),同时新节点的左右子节点为对输入两个树的相同位置的节点的左右子节点递归函数即可。

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

7. 路径总和①(112)

题目:
     路径和定义为从根节点到叶子结点的所有节点的和。
示例:
在这里插入图片描述
思路:
递归三部曲(每次使用递归考虑的三个点):

  1. 确定递归函数的参数和返回值
    传入一个数的根节点和一个值,返回True / False,可以直接使用题目定义好的函数;

  2. 确定终止条件
    当节点为NULL时,返回false,当节点的左右子节点均为空(说明该节点为叶子结点)且该节点的值等于sum,返回true;

  3. 确定单层递归的逻辑
    每层递归仅需要判断终止条件即可,若不满足则判断该节点的左右子节点是否满足,注意传入左右子节点的sum值应该为传入函数的初始sum值减去该节点的val值。

class Solution {
public:
    bool hasPathSum(TreeNode* root, int sum) {
        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);
    }
};

8. 路径总和②(113)

题目:
     给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
示例:
在这里插入图片描述

思路:
     首先根据上面的路径总和①(112),已经知道如何判断树中某一路径的和,而此题需要返回和等于特定值的路径。那么在递归每一个节点时,需要将节点的值存入一个vector中,并且不断将初始传入的sum值减去当前节点的值,若当前sum值切好为零且当前节点的左右子节点均为空(说明此节点为叶子结点),故可将此时用来保存路径值vector保存到一个vector<vector<int>> res中,用来存储最终的结果。

     如果递归到某叶子节点时,其sum值不为零,则说明此路径之和不是sum,则需要将存入vector中的此节点中的值pop掉,因为vector中存入的路径是公共使用的,pop掉不符合的节点值可以保证之前存入的路径不会被玷污掉。

class Solution {
public:
    vector<vector<int>> res;
    vector<int> temp;
    void DFS(TreeNode* root, int sum){
        if(!root) return;
        temp.push_back(root->val);
        sum -= root->val;
        if(!root->left && !root->right && !sum)
            res.push_back(temp);
        DFS(root->left, sum);
        DFS(root->right, sum);
        temp.pop_back();        
    }
    vector<vector<int>> pathSum(TreeNode* root, int sum) {
        DFS(root, sum);
        return res;
    }
};

9. 路径总和③(437)

题目:
     给定一个二叉树,它的每个结点都存放着一个整数值。找出路径和等于给定数值的路径总数。路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

示例:

思路:
     题中说它的路径必须是向下的,那么该路径不会穿过根节点,故依旧可以从根节点开始递归。按照惯例,sum不断减去每一个节点的val值,当sum值为0时说明找到了一条路径和为sum的路径;因为路径不需要从根节点开始,故需要对树进行双重递归,一次递归为寻找是否存在和为sum的路径,二次递归为遍历树中的每个节点,相当于以树中的每个节点作为寻找路径的起点进行遍历。

注意:
     handdle()中递归基只能为root==NuLL,因为树中节点的值有可能为负值,故只有递归到叶子结点时才可包含该路径的所有情况。

class Solution {
public:
    int ans = 0;
    void handdle(TreeNode* root, int sum){
        if(!root) return;
        sum -= root->val;
        if(sum == 0) ans++;
        handdle(root->left, sum);
        handdle(root->right, sum);
    }
    int pathSum(TreeNode* root, int sum) {
        if(!root) return 0;
        handdle(root, sum);
        pathSum(root->left, sum);
        pathSum(root->right, sum);
        return ans;
    }
};

10. 另一个树的子树(572)

题目:
     给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。

示例:

思路:
     本题主要解决两个问题,一是将树 t 的根节点与树 s 的某个子节点对应上,二是判断从该子节点到叶子结点ts是否完全相同。首先考虑判断从s的某子节点到叶子结点 ts是否完全相同的问题,其递归基为s与t相对应的叶子结点均为NULL且在路径上的每一个节点值均相同。其次,对于树 t 的根节点与树 s 的某个子节点对应问题,对树s本身进行递归判断即可。

class Solution {
public:
    bool isSubtreeWithRoot(TreeNode* s, TreeNode* t){
        if(!s && !t) return true;
        if(!s || !t) return false;
        if(s->val != t->val) return false;
        return isSubtreeWithRoot(s->left, t->left) && isSubtreeWithRoot(s->right, t->right);
    }
    bool isSubtree(TreeNode* s, TreeNode* t) {
        if(!s) return false;
        return isSubtreeWithRoot(s, t)||isSubtree(s->left, t)||isSubtree(s->right, t);
    }
};

11. 对称二叉树(101)

题目:
     给定一个二叉树,检查它是否是镜像对称的。
示例:

思路:
     递归基为某节点的子节点为空或者两个相比较节点的值不同;将每个节点的左子节点与它自己的右子节点进行比较即可。

class Solution {
public:
    bool handdle(TreeNode* a, TreeNode* b){
        if(!a && !b) return true;
        if(!a || !b) return false;
        if(a->val != b->val) return false;
        return handdle(a->left, b->right) && handdle(a->right, b->left);
    }
    bool isSymmetric(TreeNode* root) {
        if(!root) return true;
        TreeNode* temp = root;
        return handdle(root, temp);
    }
};

12. 左叶子之和(404)

示例:

思路:
     递归基为某节点的左子节点为叶子节点,则将该节点的左子节点的值相加到全局变量中即可。对给定树的每个节点递归一遍即可获得输出。

class Solution {
public:
    int ans=0;
    int handdle(TreeNode* node){
        if(!node) return 0;
        if(node->left && !node->left->left && !node->left->right) ans +=node->left->val;
        return handdle(node->left) + handdle(node->right);
    }
    int sumOfLeftLeaves(TreeNode* root) {
        handdle(root);
        return ans;
    }
};

13. 最长同值路径(687)

题目:
     给定一个二叉树,找到最长的路径,这个路径中的每个节点具有相同值。 这条路径可以经过也可以不经过根节点。

注意:
     两个节点之间的路径长度由它们之间的边数表示。

思路:
     首先,当有路径可以经过也可以不经过根节点这样的条件时,会想到从根节点出发并且相加左右两边的长度;

     递归基依旧是当节点为NULL时,返回0;
     单层递归的逻辑为:当前节点的左子节点不为空时且当前节点与左节点的val值相同时,对路径长度进行加1;对当前节点的右子节点同理,因为要求最长的同值路径,则单层递归输出应该为保存的左路径长度与右路径长度的最大值。

class Solution {
public:
    int path=0;
    int handdle(TreeNode* node){
        if(!node) return 0;
        int left = handdle(node->left);
        int right = handdle(node->right);
        int leftpath = ((node->left != NULL) && (node->val == node->left->val))? 1+left:0;
        int rightpath = ((node->right != NULL) && (node->val == node->right->val))? 1+right:0;
        path = max(path, leftpath+rightpath);
        return max(leftpath, rightpath);
    }
    int longestUnivaluePath(TreeNode* root) {
        handdle(root);
        return path;
    }
};

14. 找出二叉树中第二小的节点(671)

题目:
     给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话,那么该节点的值等于两个子节点中较小的一个。给出这样的一个二叉树,你需要输出所有节点中的第二小的值。如果第二小的值不存在的话,输出 -1 。

示例:

思路:
      某个根节点是其左右子节点中的最小值,我们的目的是找出整个树中第二小的节点值,一共有两种情况:

  1. 保存某根节点的左右节点的值,若根节点的值与其左节点值相等,则说明右节点是树中第二小的节点值的候选值,但其左节点的子树中可能存在比当前右节点小的值,故需要对左节点的子树进行递归得到左节点的子树中的倒数第二小的值,将这个值与保存的根节点的右节点值比较,返回这两个值中的最小值。右节点同理。
  2. 若根节点的值与左右节点的值均相等,则需要对其左右节点的子节点均进行递归操作,如果左右节点的子节点的返回值均与根节点的值相同,则返回-1;若temp值大于根节点的值,则返回temp;否则返回左右节点的子节点的返回值中较大的那个数。
class Solution {
public:
    int findSecondMinimumValue(TreeNode* root) {
        if(!root || !root->left) return -1;
        int left = root->left->val;
        int right= root->right->val;
        if(root->val==root->left->val) left = findSecondMinimumValue(root->left);
        if(root->val==root->right->val) right = findSecondMinimumValue(root->right);
        if(root->val==left && root->val==right) return -1;
        int temp = min(left, right);
        if(root->val < temp) return temp;
        else return max(left, right);
    }
};

欢迎关注【OAOA

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值