【leetcode】二叉树的几个重要的递归题目汇总附详细的解题思路

一、leetcode236

题目描述

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。

通过的代码

(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:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
     
        if(root==NULL||root==p||root==q)//递归的终止条件
        return root;
        TreeNode*left=lowestCommonAncestor(root->left,p,q);
        TreeNode*right=lowestCommonAncestor(root->right,p,q);//每层递归要做的事情
        if(left==NULL&&right==NULL)//每层递归的返回,三种情况分别讨论
        return NULL;
        else if(left!=NULL&&right!=NULL)
        return root;
        else
        return left==NULL?right:left;
    }
};

(2)我自己写的代码

/**
 * 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:
    vector<TreeNode*>route1;
    vector<TreeNode*>route2;
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
     
        vector<TreeNode*>route;
        int minSize;
        TreeNode *node;
        int i;
        route.push_back(root);
        dfs(root,route,p,q);
        minSize=min(route1.size(),route2.size());
        for(i=0;i<minSize;i++)
        {
     
            if(route1[i]!=route2[i])
            break;
        }
        node=route1[i-1];
        return node;
    }
    void dfs(TreeNode* node,vector<TreeNode*>&route,TreeNode* p, TreeNode* q)
    {
     
        if(node==NULL)
        return ;
        if(route[route.size()-1]==p)
        route1=route;
        if(route[route.size()-1]==q)
        route2=route;
        if(node->left)
        {
     
            route.push_back(node->left);
            dfs(node->left,route,p,q);
            route.pop_back();
        }
        if(node->right)
        {
     
            route.push_back(node->right);
            dfs(node->right,route,p,q);
            route.pop_back();
        }
    }
};


解题思路

这道题目我自己写的时候,我的思路是通过dfs函数得到从根节点到p和q这两个结点之间的路径,保存两个路径,然后找到两个路径中第一个不相同的结点前的最后一个相同的结点。这个想法还是很容易想到的。但是代码写出来,发现代码量还是不算少。所以我去搜了搜别人的代码,发现同样是递归,网上最好的代码非常简单,也就是上面的第一份代码。
其实思路就是,每次到达递归的某一层时,分别对当前这一层root的左侧left和右侧right进行搜索p和q结点,如果左侧left和右侧right都为NULL,说明p和q并不在当前root的左右侧,故而返回NULL;如果左侧left和右侧right都不为NULL,说明此时p和q结点肯定分别分布在root的左右侧,此时返回root,root就是我们题目里面认为的两个指定节点的最近公共祖先;如果左侧left和右侧right有一个不为NULL,说明一侧存在结点,另一侧不存在结点,此时肯定是返回不为NULL的那一侧搜索到的结点。第三种情况有点难以理解,我觉得第三种情况其实是包含了两种可能存在的情况,一种就是p和q结点全部位于root的某一侧,此时搜索到的第一个结点就是题目里面认为的两个指定节点的最近公共祖先,所以可以直接返回。还有一种情况就是,p或者q结点位于root的某一侧,此时我们可以先返回这个结点,到后面再结合之后的结果得到最后要求的两个指定节点的最近公共祖先。
感觉递归最重要的三点,一个是要知道递归的终止条件,一个是要知道每一层递归要做什么,一个是要知道每一层递归返回的是什么。这个题目跟平衡二叉树,计算二叉树的深度这些题目的解题过程比较相似,每层递归都要得到当前结点左右侧left和right的某个值,然后进行判断,返回当前层要返回的东西。
二叉树的递归的问题,现在我还没有掌握的很好,之后需要多练习。

二、leetcode226

题目描述

翻转一棵二叉树。

示例:

输入:
4
/
2 7
/ \ /
1 3 6 9

输出:
4
/
7 2
/ \ /
9 6 3 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:
    TreeNode* invertTree(TreeNode* root) {
     
        if(root==NULL)
        return NULL;
        else if(root->left==NULL&&root->right==NULL)
        return root;
        TreeNode*p;
        TreeNode*q;
        p=invertTree(root->right);
        q=invertTree(root->left);
        root->left=p;//这里不能直接写成root->left=invertTree(root->right);
        root->right=q;//这里不能直接写成root->right=invertTree(root->left);
        return root;
    }
};

解题思路

又是一个二叉树的递归题目。因为有上面第一个题目的经验了,我开始寻找递归终止的条件,每一层要做的事情以及每一层返回的结果。这个题目的终止条件就是root结点左右都为NULL的时候,也就是root结点是叶子结点的时候,因为此时可以直接返回当前结点root,而不用进行任何操作(因为没有left和right,不需要对子节点进行翻转)。每一层就是对当前root结点的左右子树进行交换,也就是题目中的翻转。并且每一层返回翻转后的root结点。上面的三步都找到了,题目就迎刃而解了。
有一个需要注意的地方,我在代码中标出来了,因为我刚开始就没有注意到。

三、leetcode543

题目描述

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

      1
     / \
    2   3
   / \     
  4   5    

返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。
注意:两结点之间的路径长度是以它们之间边的数目表示。

通过的代码

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

解题思路

这个题目跟上面的两个也很类似啊,每一层递归求解一个left,一个right,其实就是左右两侧的depth深度,当然,这里的深度是从下往上的,毕竟题目要计算的直径就是这样定义的。然后每层对于root结点对应的深度进行返回。并且在每层计算一个直径,如果大于之前保存的,就替换ans。
所以也是,找到三步要做的事情就行。而且我发现这几个题目在每一个递归层做的事情都是先分别计算left和right,然后再判断,返回。思路都差不多。

四、链接

递归算法总结
这篇博文提到了上面的三步,介绍的很好。把博文保存在这里,后面可以再看看

你可能感兴趣的:(LeetCode)