剑指offer:二叉树

一、重建二叉树

JZ7 重建二叉树
中等 通过率:27.73% 时间限制:1秒 空间限制:256M
知识点树dfs数组
描述
给定节点数为 n 的二叉树的前序遍历和中序遍历结果,请重建出该二叉树并返回它的头结点。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出如下图所示。

提示:
1.vin.length == pre.length
2.pre 和 vin 均无重复元素
3.vin出现的元素均出现在 pre里
4.只需要返回根结点,系统会自动输出整颗树做答案对比
数据范围:n≤2000,节点的值 −10000≤val≤10000
要求:空间复杂度 O(n),时间复杂度 O(n)
示例1
输入:
[1,2,4,7,3,5,6,8],[4,7,2,1,5,3,8,6]
返回值:
{1,2,3,4,#,5,6,#,7,#,#,8}
说明:
返回根节点,系统会输出整颗二叉树对比结果,重建结果如题面图示
示例2
输入:
[1],[1]
返回值:
{1}
示例3
输入:
[1,2,3,4,5,6,7],[3,2,4,1,6,5,7]
返回值:
{1,2,5,3,4,6,7}

/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution 
{
public:
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) 
    {
        if (pre.empty() || vin.empty() || pre.size() != vin.size()){
            return nullptr;
        }
        vector<int> left_pre;  // 前序遍历的左子树
        vector<int> right_pre; // 前序遍历的右子树
        vector<int> left_in;   // 中序遍历的左子树
        vector<int> right_in;  // 中序遍历的右子树
        //创建头结点,并初始化头结点的值
        TreeNode* head = new TreeNode(pre[0]);
        //寻找头结点在中序遍历中的位置
        int pos = 0;
        for (int index = 0; index < vin.size(); index++){
            if (vin[index] == pre[0]){
                pos = index;
                break;
            }
        }
        //将前序遍历和中序遍历的左子树的所有结点放入vector容器
        for (int index = 0; index < pos; index++){
            left_pre.push_back(pre[index + 1]);
            left_in.push_back(vin[index]);
        }
         //将前序遍历和中序遍历的右子树的所有结点放入vector容器
        for (int index = pos + 1; index < vin.size(); index++){
            right_pre.push_back(pre[index]);
            right_in.push_back(vin[index]);
        }
        //递归求左子树结点指针和右子树结点指针
        head->left  = reConstructBinaryTree(left_pre,  left_in);
        head->right = reConstructBinaryTree(right_pre, right_in);
        return head;
    }
};

二、序列化二叉树

JZ37 序列化二叉树
较难 通过率:24.74% 时间限制:1秒 空间限制:64M
知识点队列树
描述
请实现两个函数,分别用来序列化和反序列化二叉树,不对序列化之后的字符串进行约束,但要求能够根据序列化之后的字符串重新构造出一棵与原二叉树相同的树。

二叉树的序列化(Serialize)是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树等遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#)

二叉树的反序列化(Deserialize)是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

例如,可以根据层序遍历的方案序列化,如下图:

层序序列化(即用函数Serialize转化)如上的二叉树转为"{1,2,3,#,#,6,7}“,再能够调用反序列化(Deserialize)将”{1,2,3,#,#,6,7}"构造成如上的二叉树。

当然你也可以根据满二叉树结点位置的标号规律来序列化,还可以根据先序遍历和中序遍历的结果来序列化。不对序列化之后的字符串进行约束,所以欢迎各种奇思妙想。

数据范围:节点数 n≤100,树上每个节点的值满足 0≤val≤150
要求:序列化和反序列化都是空间复杂度 O(n),时间复杂度 O(n)
示例1
输入:
{1,2,3,#,#,6,7}
返回值:
{1,2,3,#,#,6,7}
说明:
如题面图
示例2
输入:
{8,6,10,5,7,9,11}
返回值:
{8,6,10,5,7,9,11}

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    // 序列化
    char* Serialize(TreeNode *root) {    
        if (root == NULL) {
            return NULL;
        }
        string str;
        SerializeCircle(root, str);
        char* buff = new char[str.size() + 1];
        strcpy(buff, str.c_str());
        return buff;
    }
    void SerializeCircle(TreeNode* root, string &str) {
        if (root == NULL) {
            str += "#,";
            return;
        }
        //前序遍历:根,左,右
        str += to_string(root->val) + ',';
        SerializeCircle(root->left, str);
        SerializeCircle(root->right, str);
    }

    // 反序列化
    TreeNode* Deserialize(char *str) {
        if (str == NULL) {
            return NULL;
        }
        return DeserializeCircle(&str);
    }
    TreeNode* DeserializeCircle(char** str) {
        if (**str == '#') {
            (*str) += 2;
            return NULL;
        }
        int val = 0;
        while (**str != ',' && **str != '\0') {
            val = val * 10 + (**str - 48);
            ++(*str);
        }
        TreeNode* root = new TreeNode(val);
        if (**str == '\0') {
            return root;
        } else if (**str == ',') {
            ++(*str);
        }
        root->left  = DeserializeCircle(str);
        root->right = DeserializeCircle(str);
        return root;
    }
};

三、遍历二叉树

二叉树遍历
https://blog.csdn.net/UUUUTaossienUUUU/article/details/129354318

四、打印二叉树

JZ32 从上往下打印二叉树
简单 通过率:29.76% 时间限制:1秒 空间限制:64M
知识点队列树
描述
不分行从上往下打印出二叉树的每个节点,同层节点从左至右打印。例如输入{8,6,10,#,#,2,1},
如以下图中的示例二叉树,则依次打印8,6,10,2,1(空节点不打印,跳过),请你将打印的结果存放到一个数组里面,返回。

数据范围:
0<=节点总数<=1000
-1000<=节点值<=1000
示例1
输入:
{8,6,10,#,#,2,1}
返回值:
[8,6,10,2,1]
示例2
输入:
{5,4,#,3,#,2,#,1}
返回值:
[5,4,3,2,1]

第一种方法:for循环

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        vector<int> res;
        if (root == nullptr) {
            return res;
        }
        queue<TreeNode*> que;
        que.push(root);
        while (!que.empty()) {
            int len = que.size();
            for (int i = 0; i < len; i++ ) {
                TreeNode* p = que.front();
                que.pop();
                res.push_back(p->val);
                if (p->left != nullptr) {
                    que.push(p->left);
                }
                if (p->right != nullptr) {
                    que.push(p->right);
                }
            }
        }
        return res;
    }
};

第二种方法:没有for循环

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        vector<int> res;
        if (root == nullptr) {
            return res;
        }
        queue<TreeNode*> que;
        que.push(root);
        int cur = 1;
        int next = 0;
        while (!que.empty()) {
            TreeNode* p = que.front();
            que.pop();
            res.push_back(p->val);
            if (p->left != nullptr) {
                que.push(p->left);
                next++;
            }
            if (p->right != nullptr) {
                que.push(p->right);
                next++;
            }
            cur--;
            if (cur == 0) {
                cur = next;
                next = 0;
            }
        }
        return res;
    }
};

JZ77 按之字形顺序打印二叉树
中等 通过率:28.61% 时间限制:1秒 空间限制:64M
知识点栈树队列
描述
给定一个二叉树,返回该二叉树的之字形层序遍历,(第一层从左向右,下一层从右向左,一直这样交替)

数据范围:0≤n≤1500,树上每个节点的val满足 ∣val∣<=1500
要求:空间复杂度:O(n),时间复杂度:O(n)
例如:
给定的二叉树是{1,2,3,#,#,4,5}

该二叉树之字形层序遍历的结果是
[
[1],
[3,2],
[4,5]
]
示例1
输入:
{1,2,3,#,#,4,5}
返回值:
[[1],[3,2],[4,5]]
说明:
如题面解释,第一层是根节点,从左到右打印结果,第二层从右到左,第三层从左到右。
示例2
输入:
{8,6,10,5,7,9,11}
返回值:
[[8],[10,6],[5,7,9,11]]
示例3
输入:
{1,2,3,4,5}
返回值:
[[1],[3,2],[4,5]]

第一种实现方式:有for循环

class Solution
{
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        vector<vector<int> > res;
        if (pRoot == NULL) {
            return res;
        }
        queue<TreeNode*> que;
        que.push(pRoot);
        vector<int> temp;
        bool Is = false;
        while ( !que.empty() ) {
            int len = que.size();
            for (int index = 0; index < len; index++) {
                TreeNode* p = que.front();
                que.pop();
                temp.push_back(p->val);
                if (p->left != NULL) {
                    que.push(p->left);
                }
                if (p->right != NULL) {
                    que.push(p->right);
                }
            }
            if (Is == true) {
                reverse(temp.begin(), temp.end());
                Is = false;
            } else {
                Is = true;
            }
            res.push_back(temp);
            temp.clear();

        }
        return res;
    }
};

第二种实现方式:没有for循环

class Solution
{
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        vector<vector<int> > res;
        if (pRoot == NULL) {
            return res;
        }
        queue<TreeNode*> que;
        que.push(pRoot);
        vector<int> temp;
        int next = 0;
        int curr = 1;
        bool Is = false;
        while ( !que.empty() ) {
            TreeNode* p = que.front();
            que.pop();
            temp.push_back(p->val);
            if (p->left != NULL) {
                que.push(p->left);
                ++next;
            }
            if (p->right != NULL) {
                que.push(p->right);
                ++next;
            }
            curr--;
            if (curr == 0) {   
                if (Is == true) {
                    reverse(temp.begin(), temp.end());
                    Is = false;
                } else {
                    Is = true;
                }
                res.push_back(temp);
                temp.clear();
                curr = next;
                next = 0;
            }
        }
        return res;
    }
};

JZ78 把二叉树打印成多行
中等 通过率:34.14% 时间限制:1秒 空间限制:256M
知识点树广度优先搜索(BFS)
描述
给定一个节点数为 n 二叉树,要求从上到下按层打印二叉树的 val 值,同一层结点从左至右输出,每一层输出一行,将输出的结果存放到一个二维数组中返回。
例如:
给定的二叉树是{1,2,3,#,#,4,5}

该二叉树多行打印层序遍历的结果是
[
[1],
[2,3],
[4,5]
]

数据范围:二叉树的节点数 0≤n≤1000,0≤val≤1000
要求:空间复杂度 O(n),时间复杂度 O(n)
输入描述:
给定一个二叉树的根节点
示例1
输入:
{1,2,3,#,#,4,5}
返回值:
[[1],[2,3],[4,5]]
示例2
输入:
{8,6,10,5,7,9,11}
返回值:
[[8],[6,10],[5,7,9,11]]
示例3
输入:
{1,2,3,4,5}
返回值:
[[1],[2,3],[4,5]]
示例4
输入:
{}
返回值:
[]

第一种方法:for循环

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution 
{
public:
    vector<vector<int>> Print(TreeNode* pRoot) {
        vector<vector<int> > res;
        queue<TreeNode*> que;
        if ( NULL == pRoot ){
            return res;
        }
        que.push(pRoot);
        vector<int> temp;
        while (!que.empty()) {
            int len = que.size();
            for ( int index = 0; index < len; index++ ){
                TreeNode* p = que.front();
                que.pop();
                temp.push_back(p->val);
                if ( NULL != p->left ) {
                    que.push(p->left);
                }
                if ( NULL != p->right ) {
                    que.push(p->right);
                }
            }
            res.push_back(temp);
            temp.clear();
        }
        return res;
    }
};

第二种方法:没有for循环

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution 
{
public:
    vector<vector<int>> Print(TreeNode* pRoot) {
        vector<vector<int> > res;
        queue<TreeNode*> que;
        if ( NULL == pRoot ){
            return res;
        }
        que.push(pRoot);
        vector<int> temp;
        int curr = 1;
        int next = 0;
        while (!que.empty()) {
            TreeNode* p = que.front();
            que.pop();
            temp.push_back(p->val);
            if ( NULL != p->left ) {
                que.push(p->left);
                next++;
            }
            if ( NULL != p->right ) {
                que.push(p->right);
                next++;
            }
            curr--;
            if ( 0 == curr ) {
                res.push_back(temp);
                temp.clear();
                curr = next;
                next = 0;
            }
        }
        return res;
    }
};

五、二叉树路径

JZ82 二叉树中和为某一值的路径(一)
简单 通过率:43.46% 时间限制:1秒 空间限制:64M
知识点树dfs
描述
给定一个二叉树root和一个值 sum ,判断是否有从根节点到叶子节点的节点值之和等于 sum 的路径。
1.该题路径定义为从树的根结点开始往下一直到叶子结点所经过的结点
2.叶子节点是指没有子节点的节点
3.路径只能从父节点到子节点,不能从子节点到父节点
4.总节点数目为n

例如:
给出如下的二叉树, sum=22,

返回true,因为存在一条路径 5→4→11→2的节点值之和为 22

数据范围:
1.树上的节点数满足 0≤n≤10000
2.每 个节点的值都满足 ∣val∣≤1000
要求:空间复杂度 O(n),时间复杂度 O(n)
进阶:空间复杂度 O(树的高度),时间复杂度 O(n)
示例1
输入:
{5,4,8,1,11,#,9,#,#,2,7},22
返回值:
true
示例2
输入:
{1,2},0
返回值:
false

示例3
输入:
{1,2},3
返回值:
true
示例4
输入:
{},0
返回值:
false

/**
 * struct TreeNode {
 *  int val;
 *  struct TreeNode *left;
 *  struct TreeNode *right;
 * };
 */
class Solution {
public:
    /**
     * 
     * @param root TreeNode类 
     * @param sum int整型 
     * @return bool布尔型
     */
    bool hasPathSum(TreeNode* root, int sum) {
        if (root == nullptr) {
            return false;
        }
        if (root->left == nullptr && root->right == nullptr && (sum - root->val == 0)) {
            return true;
        }
        return hasPathSum(root->left, sum - root->val) || hasPathSum(root->right, sum - root->val);
    }
};

JZ34 二叉树中和为某一值的路径(二)
中等 通过率:28.65% 时间限制:1秒 空间限制:64M
知识点树
描述
输入一颗二叉树的根节点root和一个整数expectNumber,找出二叉树中结点值的和为expectNumber的所有路径。
1.该题路径定义为从树的根结点开始往下一直到叶子结点所经过的结点
2.叶子节点是指没有子节点的节点
3.路径只能从父节点到子节点,不能从子节点到父节点
4.总节点数目为n

如二叉树root为{10,5,12,4,7},expectNumber为22

则合法路径有[[10,5,7],[10,12]]

数据范围:
树中节点总数在范围 [0, 5000] 内
-1000 <= 节点值 <= 1000
-1000 <= expectNumber <= 1000
示例1
输入:
{10,5,12,4,7},22
返回值:
[[10,5,7],[10,12]]

说明:
返回[[10,12],[10,5,7]]也是对的
示例2
输入:
{10,5,12,4,7},15
返回值:
[]
示例3
输入:
{2,3},0
返回值:
[]
示例4
输入:
{1,3,4},7
返回值:
[]

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/

class Solution {
public:
    vector<vector<int>> FindPath(TreeNode* root, int expectNumber) {
        if (root == nullptr) {
            return res;
        }
        PreOrder(root, expectNumber);
        return res;
    }
    void PreOrder(TreeNode* root, int expectNumber) {
        if (root == nullptr) {
            return;
        }
        path.push_back(root->val);
        if (root->left == nullptr && root->right == nullptr) {
            int temp = 0;
            for (int i = 0; i < path.size(); i++) {
                temp += path[i];
            }
            if (temp == expectNumber) {
                res.push_back(path);
            }
        }
        if (root->left != nullptr) {
            PreOrder(root->left, expectNumber);
        }
        if (root->right != nullptr) {
            PreOrder(root->right, expectNumber);
        }
        path.pop_back();
    }
private:
    vector<int> path;
    vector<vector<int> > res;
};

JZ84 二叉树中和为某一值的路径(三)
中等 通过率:52.12% 时间限制:1秒 空间限制:256M
知识点树
描述
给定一个二叉树root和一个整数值 sum ,求该树有多少路径的的节点值之和等于 sum 。
1.该题路径定义不需要从根节点开始,也不需要在叶子节点结束,但是一定是从父亲节点往下到孩子节点
2.总节点数目为n
3.保证最后返回的路径个数在整形范围内(即路径个数小于231-1)

数据范围:
0<=n<=1000
−109<=节点值<=109

假如二叉树root为{1,2,3,4,5,4,3,#,#,-1},sum=6,那么总共如下所示,有3条路径符合要求

示例1
输入:
{1,2,3,4,5,4,3,#,#,-1},6
返回值:
3
说明:
如图所示,有3条路径符合
示例2
输入:
{0,1},1
返回值:
2
示例3
输入:
{1,#,2,#,3},3
返回值:
2

/**
 * struct TreeNode {
 *  int val;
 *  struct TreeNode *left;
 *  struct TreeNode *right;
 *  TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 * };
 */
class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param root TreeNode类 
     * @param sum int整型 
     * @return int整型
     */
    int FindPath(TreeNode* root, int sum) {
        if (root == nullptr) {
            return res;
        }
        dfs(root, sum);
        FindPath(root->left, sum);
        FindPath(root->right, sum);
        return res;
    }
    void dfs(TreeNode* root, int sum) {
        if (root == nullptr) {
            return;
        }
        if (sum - root->val == 0) {
            res++;
        }
        dfs(root->left, sum - root->val);
        dfs(root->right, sum - root->val);
    }
private:
    int res = 0;
};

JZ8 二叉树的下一个结点
中等 通过率:31.50% 时间限制:1秒 空间限制:64M
知识点树
描述
给定一个二叉树其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的next指针。下图为一棵有9个节点的二叉树。树中从父节点指向子节点的指针用实线表示,从子节点指向父节点的用虚线表示

示例:
输入:{8,6,10,5,7,9,11},8
返回:9
解析:这个组装传入的子树根节点,其实就是整颗树,中序遍历{5,6,7,8,9,10,11},根节点8的下一个节点就是9,应该返回{9,10,11},后台只打印子树的下一个节点,所以只会打印9,如下图,其实都有指向左右孩子的指针,还有指向父节点的指针,下图没有画出来

数据范围:节点数满足 1≤n≤50 ,节点上的值满足 1≤val≤100

要求:空间复杂度 O(1) ,时间复杂度 O(n)
输入描述:
输入分为2段,第一段是整体的二叉树,第二段是给定二叉树节点的值,后台会将这2个参数组装为一个二叉树局部的子树传入到函数GetNext里面,用户得到的输入只有一个子树根节点
返回值描述:
返回传入的子树根节点的下一个节点,后台会打印输出这个节点
示例1
输入:
{8,6,10,5,7,9,11},8

返回值:
9

示例2
输入:
{8,6,10,5,7,9,11},6

返回值:
7

示例3
输入:
{1,2,#,#,3,#,4},4

返回值:
1

示例4
输入:
{5},5

返回值:
“null”

说明:
不存在,后台打印"null"

/*
存在右子树(A存在右子树,下一个结点就是B)

                 A
                    X
                  X
                X
              X
            B

不存在右子树(A不存在右子树,下一个结点就是B)

  B
X
  X
     X
        X
          A

*/

/*
struct TreeLinkNode {
    int val;
    struct TreeLinkNode *left;
    struct TreeLinkNode *right;
    struct TreeLinkNode *next;
    TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {
        
    }
};
*/
class Solution {
public:
    TreeLinkNode* GetNext(TreeLinkNode* pNode) {
        if ( pNode == nullptr) {
            return nullptr;
        }
        if ( pNode->right != nullptr ) {
            //如果存在右子树,那么下一个结点就是右子树的最左子节点 
            TreeLinkNode* p = pNode->right;
            while ( p->left != nullptr ) {
                p = p->left;
            }
            return p;
        } else {
            //如果不存在右子树,那么下一个结点就是这个结点的父结点集合中的第一个45度父结点
            //这个结点的父结点可能有多个135度父结点
            //满足条件 p->next->left == p 的第一个结点就是第一个45度父结点
            TreeLinkNode* p = pNode;
            while ( p->next != nullptr && p->next->left != p ) {
                p = p->next;
            }
            return p->next;
        }
    }
};

JZ86 在二叉树中找到两个节点的最近公共祖先
中等 通过率:48.26% 时间限制:1秒 空间限制:256M
知识点树
描述
给定一棵二叉树(保证非空)以及这棵树上的两个节点对应的val值 o1 和 o2,请找到 o1 和 o2 的最近公共祖先节点。

数据范围:树上节点数满足 1≤n≤105 , 节点值val满足区间 [0,n)
要求:时间复杂度 O(n)

注:本题保证二叉树中每个节点的val值均不相同。

如当输入{3,5,1,6,2,0,8,#,#,7,4},5,1时,二叉树{3,5,1,6,2,0,8,#,#,7,4}如下图所示:

所以节点值为5和节点值为1的节点的最近公共祖先节点的节点值为3,所以对应的输出为3。
节点本身可以视为自己的祖先
示例1
输入:
{3,5,1,6,2,0,8,#,#,7,4},5,1
返回值:
3
示例2
输入:
{3,5,1,6,2,0,8,#,#,7,4},2,7
返回值:
2

既然要找到二叉树中两个节点的最近公共祖先,那我们可以考虑先找到两个节点全部祖先,可以得到从根节点到目标节点的路径,然后依次比较路径得出谁是最近的祖先。
找到两个节点的所在可以深度优先搜索遍历二叉树所有节点进行查找。

具体做法:
step 1:利用dfs求得根节点到两个目标节点的路径:每次选择二叉树的一棵子树往下找,同时路径数组增加这个遍历的节点值。
step 2:一旦遍历到了叶子节点也没有,则回溯到父节点,寻找其他路径,回溯时要去掉数组中刚刚加入的元素。
step 3:然后遍历两条路径数组,依次比较元素值。
step 4:找到两条路径第一个不相同的节点即是最近公共祖先。

/**
 * struct TreeNode {
 *  int val;
 *  struct TreeNode *left;
 *  struct TreeNode *right;
 * };
 */
class Solution {
public:
    /**
     * 
     * @param root TreeNode类 
     * @param o1 int整型 
     * @param o2 int整型 
     * @return int整型
     */
    int lowestCommonAncestor(TreeNode* root, int o1, int o2) {
        vector<int> path1;
        vector<int> path2;
        dfs(root, path1, o1);
        flag = false;
        dfs(root, path2, o2);
        int res;
        for (int i = 0; i < path1.size() && i < path2.size(); i++) {
            if (path1[i] == path2[i]) {
                res = path1[i];
            } else {
                break;
            }
        }
        return res;
    }
    void dfs(TreeNode* root, vector<int>& path, int o) {
        if (root == nullptr || flag == true) {
            return;
        }
        path.push_back(root->val);
        if (root->val == o) {
            flag = true;
            return;
        }
        dfs(root->left, path, o);
        dfs(root->right, path, o);
        if (flag == true) {
            return;
        }
        path.pop_back();
    }
private:
    bool flag = false;
};

JZ68 二叉搜索树的最近公共祖先
简单 通过率:61.53% 时间限制:1秒 空间限制:256M
知识点树递归
描述
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
1.对于该题的最近的公共祖先定义:对于有根树T的两个节点p、q,最近公共祖先LCA(T,p,q)表示一个节点x,满足x是p和q的祖先且x的深度尽可能大。在这里,一个节点也可以是它自己的祖先.
2.二叉搜索树是若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值; 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值
3.所有节点的值都是唯一的。
4.p、q 为不同节点且均存在于给定的二叉搜索树中。
数据范围:
3<=节点总数<=10000
0<=节点值<=10000

如果给定以下搜索二叉树: {7,1,12,0,4,11,14,#,#,3,5},如下图:

示例1
输入:
{7,1,12,0,4,11,14,#,#,3,5},1,12
返回值:
7

说明:
节点1 和 节点12的最近公共祖先是7
示例2
输入:
{7,1,12,0,4,11,14,#,#,3,5},12,11
返回值:
12

说明:
因为一个节点也可以是它自己的祖先.所以输出12

具体做法:
step 1:根据二叉搜索树的性质,从根节点开始查找目标节点,当前节点比目标小则进入右子树,当前节点比目标大则进入左子树,直到找到目标节点。这个过程成用数组记录遇到的元素。
step 2:分别在搜索二叉树中找到p和q两个点,并记录各自的路径为数组。
step 3:同时遍历两个数组,比较元素值,最后一个相等的元素就是最近的公共祖先。

/**
 * struct TreeNode {
 *  int val;
 *  struct TreeNode *left;
 *  struct TreeNode *right;
 *  TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 * };
 */
class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param root TreeNode类 
     * @param p int整型 
     * @param q int整型 
     * @return int整型
     */
    int lowestCommonAncestor(TreeNode* root, int p, int q) {
        vector<int> path1 = getPath(root, p);
        vector<int> path2 = getPath(root, q);
        int res;
        for (int i = 0; i < path1.size() && i < path2.size(); i++) {
            if (path1[i] == path2[i]) {
                res = path1[i];
            } else {
                break;
            }
        }
        return res;
    }
    vector<int> getPath(TreeNode* root, int target) {
        vector<int> path;
        TreeNode* p = root;
        while (p->val != target) {
            path.push_back(p->val);
            if (p->val > target) {
                p = p->left;
            } else {
                p = p->right;
            }
        }
        path.push_back(p->val);
        return path;
    }
};

六、二叉树深度

JZ55 二叉树的深度
简单 通过率:50.09% 时间限制:1秒 空间限制:64M
知识点树
描述
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度,根节点的深度视为 1 。

数据范围:节点的数量满足 0≤n≤100 ,节点上的值满足 0≤val≤100
进阶:空间复杂度 O(1) ,时间复杂度 O(n)

假如输入的用例为{1,2,3,4,5,#,6,#,#,7},那么如下图:

示例1
输入:
{1,2,3,4,5,#,6,#,#,7}
返回值:
4
示例2
输入:
{}
返回值:
0

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/

class Solution {
public:
    int TreeDepth(TreeNode* pRoot) {
        if (pRoot == NULL) {
            return 0;
        }
        int left = TreeDepth(pRoot->left);
        int rihgt = TreeDepth(pRoot->right);
        return max(left + 1, rihgt + 1);
    }
};

七、对称二叉树

JZ28 对称的二叉树
简单 通过率:33.70% 时间限制:1秒 空间限制:64M
知识点树
描述
给定一棵二叉树,判断其是否是自身的镜像(即:是否对称)
例如: 下面这棵二叉树是对称的

下面这棵二叉树不对称。

数据范围:节点数满足 0≤n≤1000,节点上的值满足 ∣val∣≤1000
要求:空间复杂度 O(n),时间复杂度 O(n)
备注:
你可以用递归和迭代两种方法解决这个问题
示例1
输入:
{1,2,2,3,4,4,3}
返回值:
true

示例2
输入:
{8,6,9,5,7,7,5}
返回值:
false

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution 
{
public:
    bool isSymmetrical(TreeNode* pRoot) {
        if ( NULL == pRoot ){
            return true;
        }
        return isSym(pRoot->left, pRoot->right);
    }
    bool isSym(TreeNode* pRoot1, TreeNode* pRoot2) {
        //如果两个指针都为空,说明对称
        if ( NULL == pRoot1 && NULL == pRoot2 ) {
            return true;
        }
        //如果两个指针有一个为空,一个不为空,说明不对称
        if ( NULL == pRoot1 || NULL == pRoot2 ) {
            return false;
        }

        //如果两个指针都不为空,那就看结点的值是否相等
        if ( pRoot1->val != pRoot2->val ) {
            //如果不相等,说明不对称;
            return false;
        } else {
            //如果相等,需要继续往下判断
            return isSym(pRoot1->left, pRoot2->right) && isSym(pRoot1->right, pRoot2->left);
        }
    }
};

八、二叉搜索树

JZ54 二叉搜索树的第k个节点
中等 通过率:45.06% 时间限制:1秒 空间限制:256M
知识点树dfs递归
描述
给定一棵结点数为n 二叉搜索树,请找出其中的第 k 小的TreeNode结点值。
1.返回第k小的节点值即可
2.不能查找的情况,如二叉树为空,则返回-1,或者k大于n等等,也返回-1
3.保证n个节点的值不一样

数据范围: 0≤n≤1000,0≤k≤1000,树上每个结点的值满足0≤val≤1000
进阶:空间复杂度 O(n),时间复杂度 O(n)

如输入{5,3,7,2,4,6,8},3时,二叉树{5,3,7,2,4,6,8}如下图所示:

该二叉树所有节点按结点值升序排列后可得[2,3,4,5,6,7,8],所以第3个结点的结点值为4,故返回对应结点值为4的结点即可。

示例1
输入:
{5,3,7,2,4,6,8},3
返回值:
4

示例2
输入:
{},1
返回值:
-1

备注:
当树是空

/**
 * struct TreeNode {
 *  int val;
 *  struct TreeNode *left;
 *  struct TreeNode *right;
 *  TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 * };
 */

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param proot TreeNode类 
     * @param k int整型 
     * @return int整型
     */ 
    int KthNode(TreeNode* proot, int k) {
        MidOrder(proot, k);
        if (res != nullptr) {
            return res->val;
        } else {
            return -1;
        }
    }
    void MidOrder(TreeNode* root, int k) {
        if (root == nullptr || count > k) {
            return;
        }
        MidOrder(root->left, k);

        count++;
        if (count == k) {
            res = root;
            return;
        }
        
        MidOrder(root->right, k);
    }
private:
    TreeNode* res = nullptr;
    int count = 0;
};

JZ33 二叉搜索树的后序遍历序列
中等 通过率:25.41% 时间限制:1秒 空间限制:64M
知识点栈树
描述
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回 true ,否则返回 false 。假设输入的数组的任意两个数字都互不相同。

数据范围: 节点数量 0≤n≤1000 ,节点上的值满足 1≤val≤105 ,保证节点上的值各不相同
要求:空间复杂度 O(n) ,时间时间复杂度 O(n2)
提示:
1.二叉搜索树是指父亲节点大于左子树中的全部节点,但是小于右子树中的全部节点的树。
2.该题我们约定空树不是二叉搜索树
3.后序遍历是指按照 “左子树-右子树-根节点” 的顺序遍历
4.参考下面的二叉搜索树,示例 1

示例1
输入:
[1,3,2]
返回值:
true
说明:
是上图的后序遍历 ,返回true
示例2
输入:
[3,1,2]
返回值:
false
说明:
不属于上图的后序遍历,从另外的二叉搜索树也不能后序遍历出该序列 ,因为最后的2一定是根节点,前面一定是孩子节点,可能是左孩子,右孩子,根节点,也可能是全左孩子,根节点,也可能是全右孩子,根节点,但是[3,1,2]的组合都不能满足这些情况,故返回false
示例3
输入:
[5,7,6,9,11,10,8]
返回值:
true

class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
        if (sequence.size() == 0) {
            return false;
        }
        int root = sequence.back();
        vector<int> ls;
        vector<int> rs;
        //左字树,必须都要小于root,当出现大于root时,说明遇到右字树的结点了
        int i = 0;
        for (; i < sequence.size()-1; ++i) {
            if (sequence[i] > root) {
                break;
            }
            ls.push_back(sequence[i]);
        }
        //右字树,必须都要大于root,当出现小于root时,return false
        for (; i < sequence.size()-1; ++i) {
            if (sequence[i] < root) {
                return false;
            }
            rs.push_back(sequence[i]);
        }


        bool left = true;
        if (ls.size() > 0) {
            left = VerifySquenceOfBST(ls);
        }


        bool right = true;
        if (rs.size() > 0) {
            right = VerifySquenceOfBST(rs);
        }
        return left && right;
    }
};

JZ36 二叉搜索树与双向链表
中等 通过率:31.09% 时间限制:1秒 空间限制:64M
知识点分治
描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。如下图所示

数据范围:输入二叉树的节点数 0≤n≤1000,二叉树中每个节点的值 0≤val≤1000
要求:空间复杂度O(1)(即在原树上操作),时间复杂度 O(n)

注意:
1.要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继
2.返回链表中的第一个节点的指针
3.函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构
4.你不用输出双向链表,程序会根据你的返回值自动打印输出
输入描述:
二叉树的根节点
返回值描述:
双向链表的其中一个头节点。
示例1
输入:
{10,6,14,4,8,12,16}
返回值:
From left to right are:4,6,8,10,12,14,16;From right to left are:16,14,12,10,8,6,4;
说明:
输入题面图中二叉树,输出的时候将双向链表的头节点返回即可。
示例2
输入:
{5,4,#,3,#,2,#,1}
返回值:
From left to right are:1,2,3,4,5;From right to left are:5,4,3,2,1;
说明:
5
/
4
/
3
/
2
/
1
树的形状如上图

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree) {
        if (pRootOfTree == nullptr) {
            //中序递归,叶子为空则返回
            return nullptr;    
        }

        //首先递归到最左最小值  
        Convert(pRootOfTree->left);

        if (pre == nullptr){
            //找到最小值,初始化head与pre,调用一次
            head = pRootOfTree;
            pre = pRootOfTree;
        } else {
            //当前节点与上一节点建立连接,将pre设置为当前值,调用多次
            pre->right = pRootOfTree;
            pRootOfTree->left = pre;
            pre = pRootOfTree;
        }

        Convert(pRootOfTree->right);

        return head;
    }
private:
    //返回的第一个指针,即为最小值,先定为NULL
    TreeNode* head = nullptr; 
    //中序遍历当前值的上一位,初值为最小值,先定为NULL
    TreeNode* pre = nullptr;     
};

九、完全二叉树

BM35 判断是不是完全二叉树
题目
题解(74)
讨论(67)
排行
面经new
中等 通过率:38.60% 时间限制:1秒 空间限制:256M
知识点

dfs
广度优先搜索(BFS)
描述
给定一个二叉树,确定他是否是一个完全二叉树。

完全二叉树的定义:若二叉树的深度为 h,除第 h 层外,其它各层的结点数都达到最大个数,第 h 层所有的叶子结点都连续集中在最左边,这就是完全二叉树。(第 h 层可能包含 [1~2h] 个节点)

数据范围:节点数满足 1 \le n \le 100 \1≤n≤100
样例图1:

样例图2:

样例图3:

示例1
输入:
{1,2,3,4,5,6}
复制
返回值:
true
复制
示例2
输入:
{1,2,3,4,5,6,7}
复制
返回值:
true
复制
示例3
输入:
{1,2,3,4,5,#,6}
复制
返回值:
false
复制

/**
 * struct TreeNode {
 *	int val;
 *	struct TreeNode *left;
 *	struct TreeNode *right;
 *	TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 * };
 */
#include <cstdlib>
class Solution {
  public:
    bool isCompleteTree(TreeNode* root) {
        //空树一定是完全二叉树
        if(root == NULL) {
            return true;
        }
        queue<TreeNode*> que;
        que.push(root); 
        bool flag = false; 
        //层次遍历
        while(!que.empty()){ 
            int len = que.size();
            for (int i = 0; i < len; i++) {
                TreeNode* cur = que.front();
                que.pop();
                //标记第一次遇到空节点
                if (cur == NULL) {
                    flag = true;
                } else {
                    if (flag) {
                        //flag == true说明前面已经经过了叶子
                        //但是这个结点不为空,说明不是完全二叉树
                        return false;
                    }
                    que.push(cur->left);
                    que.push(cur->right);
                }
            }
        }
        return true;
    }
};

十、二叉搜素树

BM34 判断是不是二叉搜索树
题目
题解(93)
讨论(84)
排行
面经new
中等 通过率:32.96% 时间限制:1秒 空间限制:256M
知识点

描述
给定一个二叉树根节点,请你判断这棵树是不是二叉搜索树。

二叉搜索树满足每个节点的左子树上的所有节点均小于当前节点且右子树上的所有节点均大于当前节点。

例:

图1

图2

数据范围:节点数量满足 1 \le n\le 10^4 \1≤n≤10
4
,节点上的值满足 -2^{31} \le val \le 2^{31}-1\−2
31
≤val≤2
31
−1
示例1
输入:
{1,2,3}
复制
返回值:
false
复制
说明:
如题面图1
示例2
输入:
{2,1,3}
复制
返回值:
true
复制
说明:
如题面图2

/**
 * struct TreeNode {
 *	int val;
 *	struct TreeNode *left;
 *	struct TreeNode *right;
 *	TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 * };
 */
class Solution {
public:
    int pre = INT_MIN;
    //中序遍历
    bool isValidBST(TreeNode* root) {
        if(root == NULL) {
            return true;
        }

        //先进入左子树
        if(!isValidBST(root->left)) {
            return false;
        }

        //判断是否满足条件
        if(root->val <= pre) {
            return false;
        }
        //更新最大值
        pre = root->val;  

        //再进入右子树
        if(!isValidBST(root->right)) {
            return false;
        }

        return true;
    }
};

十一、平衡二叉树

JZ79 判断是不是平衡二叉树
简单 通过率:39.12% 时间限制:1秒 空间限制:64M
知识点树dfs
描述
输入一棵节点数为 n 二叉树,判断该二叉树是否是平衡二叉树。
在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树
平衡二叉树(Balanced Binary Tree),
具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
样例解释:

样例二叉树如图,为一颗平衡二叉树
注:我们约定空树是平衡二叉树。

数据范围:n≤100,树上节点的val值满足 0≤n≤1000
要求:空间复杂度O(1),时间复杂度 O(n)
输入描述:
输入一棵二叉树的根节点
返回值描述:
输出一个布尔类型的值
示例1
输入:
{1,2,3,4,5,6,7}
返回值:
true
示例2
输入:
{}
返回值:
true

class Solution {
public:
    bool IsBalanced_Solution(TreeNode* pRoot) {
        if (pRoot == nullptr) {
            return true;
        }
        int left = Depth(pRoot->left);
        int right = Depth(pRoot->right);
        int diff = left - right;
        if (diff < -1 || diff > 1) {
            return false;
        } else {
            return IsBalanced_Solution(pRoot->left) && IsBalanced_Solution(pRoot->right);
        }
    }
    int Depth(TreeNode* pRoot) {
        if (pRoot == nullptr) {
            return 0;
        }
        int left = Depth(pRoot->left);
        int right = Depth(pRoot->right);
        return max(left + 1, right + 1);
    }
};

十二、二叉树镜像

JZ27 二叉树的镜像
简单 通过率:68.22% 时间限制:1秒 空间限制:256M
知识点树
描述
操作给定的二叉树,将其变换为源二叉树的镜像。
数据范围:二叉树的节点数 0≤n≤1000 , 二叉树每个节点的值 0≤val≤1000
要求: 空间复杂度 O(n) 。本题也有原地操作,即空间复杂度 O(1) 的解法,时间复杂度 O(n)

比如:
源二叉树

镜像二叉树

示例1
输入:
{8,6,10,5,7,9,11}
返回值:
{8,10,6,11,9,7,5}
说明:
如题面所示
示例2
输入:
{}
返回值:
{}

/**
 * struct TreeNode {
 *  int val;
 *  struct TreeNode *left;
 *  struct TreeNode *right;
 *  TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 * };
 */
class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pRoot TreeNode类 
     * @return TreeNode类
     */
    TreeNode* Mirror(TreeNode* pRoot) {
        if (pRoot == nullptr) {
            return nullptr;
        }

        TreeNode* temp = pRoot->left;
        pRoot->left = pRoot->right;
        pRoot->right = temp;

        Mirror(pRoot->left);
        Mirror(pRoot->right);

        return pRoot;
    }
};

十三、二叉树子结构

JZ26 树的子结构
中等 通过率:25.58% 时间限制:1秒 空间限制:64M
知识点二叉树树
描述
输入两棵二叉树A,B,判断B是不是A的子结构。(我们约定空树不是任意一个树的子结构)
假如给定A为{8,8,7,9,2,#,#,#,#,4,7},B为{8,9,2},2个树的结构如下,可以看出B是A的子结构

数据范围:
0 <= A的节点个数 <= 10000
0 <= B的节点个数 <= 10000
示例1
输入:
{8,8,7,9,2,#,#,#,#,4,7},{8,9,2}
返回值:
true
示例2
输入:
{1,2,3,4,5},{2,4}
返回值:
true
示例3
输入:
{1,2,3},{3,1}
返回值:
false

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
class Solution 
{
public:
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) {
        if ( nullptr == pRoot1 || nullptr == pRoot2 ) {
            return false;
        }
        return DFS(pRoot1, pRoot2) || HasSubtree(pRoot1->left, pRoot2) || HasSubtree(pRoot1->right, pRoot2);
    }
    bool DFS(TreeNode *pRoot1, TreeNode *pRoot2) {
        // 必须先判断pRoot2,再判断pRoot1
        //B先到达叶结点,A可能到达叶结点,也可能没有到达叶结点,说明B是A的子结构
        if ( nullptr == pRoot2 ) {
            return true;
        }
        
        //B没有到达叶结点,而A到达叶结点,说明B不是A的子结构
        if ( nullptr == pRoot1 ) {
            return false;
        }
        
        //A和B都没有到达叶结点
        //判断两个结点的值是否相等
        //如果两个结点的值不相等,不用再往下看了,B不是A的子结构
        if (pRoot1->val != pRoot2->val) {
            return false;
        }
        
        //如果两个结点的值相等,还要继续看下面的子树的结点的值是否相同
        return DFS(pRoot1->left, pRoot2->left) && DFS(pRoot1->right, pRoot2->right);
    }
};

十四、合并二叉树

BM32 合并二叉树
题目
题解(108)
讨论(93)
排行
面经new
简单 通过率:72.69% 时间限制:1秒 空间限制:256M
知识点

描述
已知两颗二叉树,将它们合并成一颗二叉树。合并规则是:都存在的结点,就将结点值加起来,否则空的位置就由另一个树的结点来代替。例如:
两颗二叉树是:
Tree 1

                                                                    Tree 2

                                                                合并后的树为

数据范围:树上节点数量满足 0 \le n \le 5000≤n≤500,树上节点的值一定在32位整型范围内。
进阶:空间复杂度 O(1)O(1) ,时间复杂度 O(n)O(n)
示例1
输入:
{1,3,2,5},{2,1,3,#,4,#,7}
复制
返回值:
{3,4,5,5,4,#,7}
复制
说明:
如题面图
示例2
输入:
{1},{}
复制
返回值:
{1}
复制

/**
 * struct TreeNode {
 *	int val;
 *	struct TreeNode *left;
 *	struct TreeNode *right;
 * };
 */

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        if (t1 == NULL) {
            return t2;
        }
        if (t2 == NULL) {
            return t1;
        }
        //前序遍历:根左右
        TreeNode* head = new TreeNode(t1->val + t2->val);
        head->left = mergeTrees(t1->left, t2->left);
        head->right = mergeTrees(t1->right, t2->right);
        return head;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值