• 八、二叉树题目相关


    学习来源:
    代码随香炉:https://www.programmercarl.com/
    labuladong算法:https://labuladong.github.io/algo/

    二叉树

    在这里插入图片描述

    基本理论

    二叉树的种类、存储方式、遍历方式、定义方式

    二叉树的种类

    1. 满二叉树
      如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
      深度为k,有2^k-1个节点的二叉树。

    2. 完全二叉树
      完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。

    3. 二叉搜索树
      前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树。

    若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
    若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
    它的左、右子树也分别为二叉排序树

    1. 平衡二叉搜索树
      平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
      C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树,所以map、set的增删操作时间时间复杂度是logn
      unordered_map、unordered_set,unordered_map、unordered_map底层实现是哈希表。

    二叉树的存储方式

    二叉树可以链式存储,也可以顺序存储。
    如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。

    二叉树的遍历方式

    深度优先遍历
    前序遍历(递归法,迭代法)
    中序遍历(递归法,迭代法)
    后序遍历(递归法,迭代法)
    广度优先遍历
    层次遍历(迭代法)

    二叉树的定义

    struct TreeNode {
        int val;
        TreeNode *left;
        TreeNode *right;
        TreeNode(int x) : val(x), left(NULL), right(NULL) {}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    遍历方式

    前中后遍历

    前序遍历

    class Solution {
    public:
        void traversal(TreeNode* cur, vector& vec) {
            if (cur == NULL) return;
            vec.push_back(cur->val);    // 中
            traversal(cur->left, vec);  // 左
            traversal(cur->right, vec); // 右
        }
        vector preorderTraversal(TreeNode* root) {
            vector result;
            traversal(root, result);
            return result;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    中序遍历

    void traversal(TreeNode* cur, vector& vec) {
        if (cur == NULL) return;
        traversal(cur->left, vec);  // 左
        vec.push_back(cur->val);    // 中
        traversal(cur->right, vec); // 右
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    后序遍历

    void traversal(TreeNode* cur, vector& vec) {
        if (cur == NULL) return;
        traversal(cur->left, vec);  // 左
        traversal(cur->right, vec); // 右
        vec.push_back(cur->val);    // 中
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    二叉树的层序遍历

    在这里插入图片描述

    1.二叉树的层序遍历

    在这里插入图片描述

    2. 二叉树的层次遍历 II (其节点值自底向上的层次遍历)

    相对于102.二叉树的层序遍历,就是最后把result数组反转一下就可以了。

    在这里插入图片描述

    3. 二叉树的右视图

    想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

    在这里插入图片描述

    4.二叉树的层平均值

    给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。

    在这里插入图片描述

    5.N叉树的层序遍历
    
    // Definition for a Node.
    class Node {
    public:
        int val;
        vector<Node*> children;
    
        Node() {}
    
        Node(int _val) {
            val = _val;
        }
    
        Node(int _val, vector<Node*> _children) {
            val = _val;
            children = _children;
        }
    };
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    6. 在每个树行中找最大值在这里插入图片描述
    7. 填充每个节点的下一个右侧节点指针

    给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点.

    本题依然是层序遍历,只不过在单层遍历的时候记录一下本层的头部节点,然后在遍历的时候让前一个节点指向本节点就可以了

    struct Node {
      int val;
      Node *left;
      Node *right;
      Node *next;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    在这里插入图片描述

    8. 二叉树的最大深度

    在这里插入图片描述

    9. 二叉树的最小深度

    需要注意的是,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点

    在这里插入图片描述

    二叉树的属性

    对称二叉树

    给你一个二叉树的根节点 root , 检查它是否轴对称。

    在这里插入图片描述

    递归法

    在这里插入图片描述

    • 层序遍历,然后对每一层进行对称判断
    • 进行左右节点的判断 注意节点为空的情况,如果相等且不为空再进入下一层,

    迭代
    使用队列
    在这里插入图片描述

    相同的树

    在这里插入图片描述

    判断一棵树是不是另外一颗树的子树

    定义两个递归函数:
    第一个,判断是不是相同的树
    第二个,判断是不是子树,s和t都有值返回真,s和t有一个为空的另外一个有值返回false ; 最后对 两棵树的根节点 和 子节点进行比较递归
    在这里插入图片描述

    二叉树最大深度

    在这里插入图片描述

    • 后序遍历,后序max(…)+1
    • 层序遍历 每层遍历 +1

    后序遍历

    在这里插入图片描述

    层序遍历

    在这里插入图片描述

    二叉树的最小深度

    在这里插入图片描述

    • 后序遍历 需要判断左子树 或者 右子树 为空的情况 特殊考虑 1+左右,其他情况和最大深度一样
    • 层序遍历 中间加上最小的判断 如果为空直接return res

    层序遍历
    在这里插入图片描述

    完全二叉树的节点个数

    给出一个完全二叉树,求出该树的节点个数。

    普通二叉树的求法
    完全二叉树性质的求法

    1. 普通二叉树的求法

    递归
    在这里插入图片描述

    迭代法

    在这里插入图片描述

    时间复杂度:O(n)
    空间复杂度:O(n)

    1. 满二叉树的公式求解
      情况一:就是满二叉树
      情况二:最后一层叶子节点没有满。

    对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。
    对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。
    在这里插入图片描述

    • 普通二叉树遍历方式 bfs dfs
    • 根据特性的遍历方式 左子树的高度 右子树的高度 如果两者相等就直接利用公式 如果不等则递归

    平衡二叉树

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

    1. 明确递归函数的参数和返回值
      // -1 表示已经不是平衡二叉树了,否则返回值是以该节点为根节点树的高度
      int getHeight(TreeNode* node)

    2. if (node == NULL) {
      return 0;
      }

    3. 明确单层递归的逻辑
      分别求出其左右子树的高度,然后如果差值小于等于1,则返回当前二叉树的高度,否则则返回-1,表示已经不是二叉平衡树了。

    在这里插入图片描述

    二叉树所有路径

    给定一个二叉树,返回所有从根节点到叶子节点的路径。
    说明: 叶子节点是指没有子节点的节点。

    在这里插入图片描述在这里插入图片描述

    二叉树的直径

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

    
    class Solution {
    public:
        int ans;
        int depth(TreeNode* rt){
            if (rt == NULL) {
                return 0; // 访问到空节点了,返回0
            }
            int L = depth(rt->left); // 左儿子为根的子树的深度
            int R = depth(rt->right); // 右儿子为根的子树的深度
            ans = max(ans, L + R + 1); // 计算d_node即L+R+1 并更新ans
            return max(L, R) + 1; // 返回该节点为根的子树的深度
        }
        int diameterOfBinaryTree(TreeNode* root) {
            ans = 0;
            depth(root);
            return ans - 1; // 求路径,不是求节点个数,要减去1
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    左叶子之和

    给定二叉树的根节点 root ,返回所有左叶子之和。

    如果左节点不为空,且左节点没有左右孩子,那么这个节点的左节点就是左叶子
    判断当前节点是不是左叶子是无法判断的,必须要通过节点的父节点来判断其左孩子是不是左叶子

    当遇到左叶子节点的时候,记录数值,然后通过递归求取左子树左叶子之和,和 右子树左叶子之和,相加便是整个树的左叶子之和。

    在这里插入图片描述

    树左下角的值

    给定一个二叉树,在树的最后一行找到最左边的值。

    层序遍历 找树的最底层左边的值即可

    在这里插入图片描述

    路径总和(是否存在路径)

    给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false

    1. 参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。
      遍历的路线,并不要遍历整棵树,所以递归函数需要返回值,可以用bool类型表示。

    不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。
    如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。
    如果遍历到了叶子节点,count不为0,就是没找到。

    因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。

    在这里插入图片描述

    二叉树中的最大路径和

    路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。

    路径和 是路径中各节点值的总和。

    给你一个二叉树的根节点 root ,返回其 最大路径和 。

    在这里插入图片描述

    class Solution {
    public:
        int res = INT_MIN; 
        int maxPathSum(TreeNode* root) {
            if(root==nullptr){
                return 0;
            }
            oneSideMax(root);
            return res;
        }
    
         // 定义:计算从根节点 root 为起点的最大单边路径和
        int oneSideMax(TreeNode* root){
            if(root==nullptr){
                return 0;
            }
            int lmax = max(0,oneSideMax(root->left));
            int rmax = max(0,oneSideMax(root->right));
            // 后序遍历位置,顺便更新最大路径和
            int sums = lmax+rmax+root->val;
            res = max(res,sums);
            // 实现函数定义,左右子树的最大单边路径和加上根节点的值
            // 就是从根节点 root 为起点的最大单边路径和
            return max(lmax,rmax)+root->val;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    二叉树的修改与改造

    二叉树的序列化与反序列化

    /*
    struct TreeNode {
        int val;
        struct TreeNode *left;
        struct TreeNode *right;
        TreeNode(int x) :
                val(x), left(NULL), right(NULL) {
        }
    };
    
    */
    class Solution {
    private:
    	string SerializeCore(TreeNode* root) {
    		if(root == nullptr) {
                return "#!";
            }
            
            string str;
            str +=to_string(root->val) + '!';
            str +=SerializeCore(root->left);
            str +=SerializeCore(root->right);
    		return str;
    	}
    
    	TreeNode* DeserializeCore(char*& str) {
    		if(*str == '#'){
                str++;
                return nullptr;
            }
            int num = 0;
            while( *str != '!'){
                num = num*10 + (*str)-'0';
                str++;
            }
            TreeNode *node = new TreeNode(num);
            node->left = DeserializeCore(++str);
            node->right = DeserializeCore(++str);
            
            return node;
    	}
    
    public:
    	char* Serialize(TreeNode* root) {
    		string str = SerializeCore(root);
            char *chs = new char[str.size()];
            for(int i = 0;i<str.size();++i){
                chs[i] = str[i];
            }
            return chs;
    
    	}
    
    	TreeNode* Deserialize(char* str) {
    		return DeserializeCore(str);
    	}
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    翻转二叉树

    给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
    在这里插入图片描述

    前序遍历

    在这里插入图片描述

    广度优先遍历

    在这里插入图片描述

    从中序与后序遍历序列构造二叉树

    第一步:如果数组大小为零的话,说明是空节点了。

    第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。

    第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点

    第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)

    第五步:切割后序数组,切成后序左数组和后序右数组

    第六步:递归处理左区间和右区间

    在这里插入图片描述

    在这里插入图片描述

    从前序与中序遍历序列构造二叉树

    在这里插入图片描述

    在这里插入图片描述

    最大二叉树

    给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下:

    二叉树的根是数组中的最大元素。
    左子树是通过数组中最大值左边部分构造出的最大二叉树。
    右子树是通过数组中最大值右边部分构造出的最大二叉树。

    在这里插入图片描述

    合并二叉树

    在这里插入图片描述

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

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

    思路
    不要陷进去思考,从递归函数的含以上思考问题,然后把每个节点的操作 思考清楚
    给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。

    在这里插入图片描述

    二叉搜索树的属性

    二叉搜索树中的搜索

    给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。

    递归

    在这里插入图片描述

    验证二叉搜索树

    给定一个二叉树,判断其是否是一个有效的二叉搜索树。

    假设一个二叉搜索树具有如下特征:

    节点的左子树只包含小于当前节点的数。
    节点的右子树只包含大于当前节点的数。
    所有左子树和右子树自身必须也是二叉搜索树。

    中序遍历,从前往后走

    在这里插入图片描述
    在这里插入图片描述

    二叉搜索树的最小绝对值之差

    给你一棵所有节点为非负值的二叉搜索树,请你计算树中任意两节点的差的绝对值的最小值。
    提示:树中至少有 2 个节点。

    思路:
    方法一 中序遍历 数组 求差 然后求解
    方法二 使用一个临时节点保存前一个值,然后中序遍历过程中求解

    在这里插入图片描述
    在这里插入图片描述

    二叉搜索树中的众数

    给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。

    假定 BST 有如下定义:
    结点左子树中所含结点的值小于等于当前结点的值
    结点右子树中所含结点的值大于等于当前结点的值
    左子树和右子树都是二叉搜索树

    1. 如果不是二叉搜索树:
      在这里插入图片描述

    2. 是二叉搜索树
      既然是搜索树,它中序遍历就是有序的。

    把二叉搜索树转换为累加树

    给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),
    使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。

    提醒一下,二叉搜索树满足下列约束条件:

    节点的左子树仅包含键 小于 节点键的节点。
    节点的右子树仅包含键 大于 节点键的节点。
    左右子树也必须是二叉搜索树。

    类似于二叉搜索树的第k个大的树的做法
    将中序遍历的步骤,倒转一下

    中序遍历 从小到大
    中序遍历先访问右边 从大到小

    一边统计序列,一边统计前缀和

    逆二叉树遍历
    在这里插入图片描述

    二叉树的公共祖先问题

    二叉树的最近公共祖先

    给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

    在这里插入图片描述
    先给出递归函数的定义:给该函数输入三个参数 root,p,q,它会返回一个节点:

    情况 1,如果 p 和 q 都在以 root 为根的树中,函数返回的即使 p 和 q 的最近公共祖先节点。

    情况 2,那如果 p 和 q 都不在以 root 为根的树中怎么办呢?函数理所当然地返回 null 呗。

    情况 3,那如果 p 和 q 只有一个存在于 root 为根的树中呢?函数就会返回那个节点。

    • 根据这个定义,分情况讨论:

    情况 1,如果 p 和 q 都在以 root 为根的树中,那么 left 和 right 一定分别是 p 和 q(从 base case 看出来的)。

    情况 2,如果 p 和 q 都不在以 root 为根的树中,直接返回 null。

    情况 3,如果 p 和 q 只有一个存在于 root 为根的树中,函数返回该节点。

    class Solution {
    public:
        TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
            if (root == q || root == p || root == NULL) return root;
            TreeNode* left = lowestCommonAncestor(root->left, p, q);
            TreeNode* right = lowestCommonAncestor(root->right, p, q);
            if (left != NULL && right != NULL) return root;
    
            if (left == NULL && right != NULL) return right;
            else if (left != NULL && right == NULL) return left;
            else  { //  (left == NULL && right == NULL)
                return NULL;
            }
    
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    二叉搜索树的修改与改造

    二叉搜索树的插入

    在这里插入图片描述
    只要按照二叉搜索树的规则去遍历,遇到空节点就插入节点就可以了。

    到这里,大家应该能感受到,如何通过递归函数返回值完成了新加入节点的父子关系赋值操作了,下一层将加入节点返回,本层用root->left或者root->right将其接住。

    class Solution {
    public:
        TreeNode* insertIntoBST(TreeNode* root, int val) {
            // 找到空位置插入新节点
            if (root == nullptr) return new TreeNode(val);
    
            if (root->val < val)
                root->right = insertIntoBST(root->right, val);
            if (root->val > val)
                root->left = insertIntoBST(root->left, val);
            return root;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    二叉搜索树的删除

    搜索树的节点删除要比节点增加复杂的多,有很多情况需要考虑,做好心里准备。

    方案一:普通二叉树的比删除方式

    第一次是和目标节点的右子树最左面节点交换。
    第二次直接被NULL覆盖了。

    class Solution {
    public:
        TreeNode* deleteNode(TreeNode* root, int key) {
            if (root == nullptr) return root;
            if (root->val == key) {
                if (root->right == nullptr) { 
                // 这里第二次操作目标值:最终删除的作用
                    return root->left;
                }
                TreeNode *cur = root->right;
                while (cur->left) {
                    cur = cur->left;
                }
                swap(root->val, cur->val); 
                // 这里第一次操作目标值:交换目标值其右子树最左面节点。
            }
            root->left = deleteNode(root->left, key);
            root->right = deleteNode(root->right, key);
            return root;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    方案二:搜索树的删除方式

    1. 确定递归函数参数以及返回值

    遇到空返回,其实这也说明没找到删除的节点,遍历到空节点直接返回了
    if (root == nullptr) return root;

    1. 确定单层递归的逻辑
      这里就把二叉搜索树中删除节点遇到的情况都搞清楚。

    在这里插入图片描述

    二叉搜索树的构造

    在这里插入图片描述

  • 相关阅读:
    Linux中for循环
    如何用JAVA写一个Windows服务
    软件测试实战项目,问题答疑
    idea项目一直在build
    flask-vue-sqlite3-->api接口-优化版本
    SAP Scripting Tracker基本使用技巧
    数据结构绪论、顺序表课后练习题
    el-select应用虚拟列表,避免过多数据导致浏览器卡死
    架构师之路4. 浪潮LG - 面试
    awk实战演练
  • 原文地址:https://blog.csdn.net/rayso9898/article/details/127137777