八、二叉树题目相关

学习来源:
代码随香炉: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) {}
};

遍历方式

前中后遍历

前序遍历

class Solution {
public:
    void traversal(TreeNode* cur, vector<int>& vec) {
        if (cur == NULL) return;
        vec.push_back(cur->val);    // 中
        traversal(cur->left, vec);  // 左
        traversal(cur->right, vec); // 右
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root, result);
        return result;
    }
};

中序遍历

void traversal(TreeNode* cur, vector<int>& vec) {
    if (cur == NULL) return;
    traversal(cur->left, vec);  // 左
    vec.push_back(cur->val);    // 中
    traversal(cur->right, vec); // 右
}

后序遍历

void traversal(TreeNode* cur, vector<int>& vec) {
    if (cur == NULL) return;
    traversal(cur->left, vec);  // 左
    traversal(cur->right, vec); // 右
    vec.push_back(cur->val);    // 中
}

二叉树的层序遍历

在这里插入图片描述

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;
    }
};
*/

在这里插入图片描述

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

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

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

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

在这里插入图片描述

在这里插入图片描述

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
    }
};

左叶子之和

给定二叉树的根节点 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;
    }
};

二叉树的修改与改造

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

/*
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);
	}
};

翻转二叉树

给你一棵二叉树的根节点 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;
        }

    }
};

二叉搜索树的修改与改造

二叉搜索树的插入

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

到这里,大家应该能感受到,如何通过递归函数返回值完成了新加入节点的父子关系赋值操作了,下一层将加入节点返回,本层用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;
    }
};

二叉搜索树的删除

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

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

第一次是和目标节点的右子树最左面节点交换。
第二次直接被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. 确定递归函数参数以及返回值

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

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

在这里插入图片描述

二叉搜索树的构造

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ray Song

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值