当前位置: 首页 > news >正文

代码随想录二叉树篇(含源码)

二叉树与递归

  • 前言
    • 226.翻转二叉树
      • 算法思路及代码
        • solution 1 用分解问题的思路来解决
        • solution 2 用遍历的思路来解决
    • 101.对称二叉树
      • 算法思路及代码
        • solution
    • 104.二叉树的最大深度
      • 算法思路及代码
        • solution 1 遍历
        • solution 2 分解问题
    • 111.二叉树的最小深度
      • 算法思路及代码
        • solution 1
        • solution 2
    • 222.完全二叉树的结点个数
      • 算法思路和代码
        • solution 1
        • solution 2
    • 110 平衡二叉树
      • 算法思路及代码
        • solution
    • 257 二叉树的所有路径
      • 算法思路及代码
    • 404 左叶子之和
      • 算法思路与代码
        • solution
    • 513 找树左下角的值
      • 算法思路及代码
        • solution1 (遍历)
        • solution 2(迭代)
    • 112.路径总和
      • 算法思路以及代码
        • solution 1
          • 暴力
        • solution 2
    • 106 中序与后序遍历序列构造二叉树
      • 思路及代码
        • solution
        • 相关题目
    • 654 最大二叉树
      • 思路及代码
        • solution
  • 二叉搜索树相关
    • 700.二叉搜索树中的搜索
      • 算法思路与代码
        • solution

前言

本文是基于代码随想录上二叉树章节的所有例题及其对应的算法思路(序号代表的是力扣题号)

为了避免很多读者看不到最后 我写在最前面 文章总字数预计超过25000字 制作不易 这些都是我在看了很多视频之后整理而成的 希望先收藏点赞起来 以免后续丢失

算法思路主要参考(B站):灵神 代码随想录 labuladong 懒猫老师 想象力少年 在此跪谢!!!
所有题目讲解视频都可以在这里找到
视频合集

不知道从哪入手的 参考我的学习顺序

  • 1 懒猫老师的二叉树章节视频
  • 2 labuladong二叉树纲领篇
  • 3 刷题配合着灵神和官方讲解视频

labuladong二叉树纲领篇——遇到一道二叉树的题目时的通用思考过程是:
1、是否可以通过遍历一遍二叉树得到答案?如果可以,用一个 traverse 函数配合外部变量来实现。
2、是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案?如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值。
3、无论使用哪一种思维模式,你都要明白二叉树的每一个节点需要做什么,需要在什么时候(前中后序)做。

226.翻转二叉树

在这里插入图片描述

算法思路及代码

solution 1 用分解问题的思路来解决

是我在没看题解之前自己想出来的方法
我是怎么想的呢:
我们既然需要翻转整颗二叉树那么我们不妨先翻转左子树,在翻转右子树,然后将跟节点的左右子树交换即可,这样就好啦
递归的结束条件就是节点为空时结束

solution 2 用遍历的思路来解决

这题我们只需要遍历一遍二叉树就可以解决问题,为什么这么说呢?
单独抽出来一个结点来 我们只需要交换左右结点 对不对? 而且我们需要在往下去递归之前做这件事情,也就是在前序位置去交换左右节点 然后其余的结点交给递归就好

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution 1 {
public:TreeNode* invertTree(TreeNode* root) {if(root==nullptr){return nullptr;}TreeNode* right= invertTree(root->left);TreeNode* left= invertTree(root->right);root->right=right;root->left=left;return root;}
};
class Solution 2 {
public:// 主函数TreeNode* invertTree(TreeNode* root) {// 遍历二叉树,交换每个节点的子节点traverse(root);return root;}// 二叉树遍历函数void traverse(TreeNode* root) {if (root == nullptr) {return;}// *** 前序位置 ***// 每一个节点需要做的事就是交换它的左右子节点TreeNode* tmp = root->left;root->left = root->right;root->right = tmp;// 遍历框架,去遍历左右子树的节点traverse(root->left);traverse(root->right);}
};

101.对称二叉树

在这里插入图片描述

算法思路及代码

对于这题 我们先不管代码怎么实现,我们是不是同样遍历一遍二叉树就可以把这个问题解决掉 我们只需要每往下遍历以后 比较它的左右子树即可(后序位置上)
在这里插入图片描述

solution
class Solution {
public:bool compare(TreeNode* left, TreeNode* right) {// 首先排除空节点的情况if (left == NULL && right != NULL) return false;else if (left != NULL && right == NULL) return false;else if (left == NULL && right == NULL) return true;// 排除了空节点,再排除数值不相同的情况else if (left->val != right->val) return false;// 此时就是:左右节点都不为空,且数值相同的情况// 此时才做递归,做下一层的判断bool outside = compare(left->left, right->right);   // 左子树:左、 右子树:右bool inside = compare(left->right, right->left);    // 左子树:右、 右子树:左bool isSame = outside && inside;                    // 左子树:中、 右子树:中 (逻辑处理)return isSame;}bool isSymmetric(TreeNode* root) {if (root == NULL) return true;return compare(root->left, root->right);}
};

104.二叉树的最大深度

在这里插入图片描述

算法思路及代码

solution 1 遍历

对于这道题目我们首先是不是同样可以用遍历的思路来解决,终止条件就是到达叶子节点,我们只需要一个traverse函数和一个外部变量来实现就行,在刚刚进入一个结点的时候(前序位置)去更新我们的maxdepth,在离开一个节点之前(后序位置)去维护depth。

class Solution {public:int depth = 0;int res = 0;int maxDepth(TreeNode* root) {traverse(root);return res;}// 遍历二叉树void traverse(TreeNode* root) {if (root == nullptr) {return;}// 前序遍历位置depth++;// 遍历的过程中记录最大深度res = max(res, depth);traverse(root->left);traverse(root->right);// 后序遍历位置depth--;}
};
solution 2 分解问题

我们要求一颗二叉树的最大深度,我们只需要分别知道它的左右子树的最大深度即可,然后return 1(根节点)+max(leftmax,rightmax)就行了

class Solution2 {// 定义:输入一个节点,返回以该节点为根的二叉树的最大深度
public:int maxDepth(TreeNode* root) {if (root == nullptr) {return 0;}int leftMax = maxDepth(root->left);int rightMax = maxDepth(root->right);// 根据左右子树的最大深度推出原二叉树的最大深度return 1 + max(leftMax, rightMax);}
};

111.二叉树的最小深度

在这里插入图片描述

算法思路及代码

solution 1
// 「遍历」的递归思路
class Solution {
private:int minDepthValue = INT_MAX;int currentDepth = 0;void traverse(TreeNode* root) {if (root == nullptr) {return;}// 做选择:在进入节点时增加当前深度currentDepth++;// 如果当前节点是叶子节点,更新最小深度if (root->left == nullptr && root->right == nullptr) {minDepthValue = std::min(minDepthValue, currentDepth);}traverse(root->left);traverse(root->right);// 撤销选择:在离开节点时减少当前深度currentDepth--;}public:int minDepth(TreeNode* root) {if (root == nullptr) {return 0;}traverse(root);return minDepthValue;}
};
solution 2

我觉得有了上一题的铺垫首先应该是想到这种办法,思路也非常明确,就是想找到左右子树各自的最小深度然后再加上根节点 但是力扣给的示例没有全部通过,它给的示例是一颗斜树,准确的来说是条五个节点的链表,这种就是极端的情况,我们怎么去解决它呢?
根本问题就是要防止当左子树为空时直接return 0的情况发生(参照下面的预期结果),所以我们就需要在即将离开一个节点之前判断此时的左右子树是否为空(也就是leftmin/rightmin为0的情况)
例如 leftmin=0,那么我们就return 1+rightmin(参考根节点的情况)就行了
那为什么在求最大深度的时候不会出现这个问题呢?
给我的感觉: 是因为在求最大深度时,我们的出口是到叶子节点就行(一股脑的往下冲),不会面临着是否为空的问题,
而我们要求最小深度时其实更像是一个探险的人,需要一步一步地走,需要避开危险

deepseek:
最小深度中,当左子树为空时(root.left为None),右子树的深度决定了当前节点的最小深度。同理处理右子树为空的情况。只有左右子树均存在时,才取较小值加1,确保路径有效。(关键)
最大深度直接取左右子树的最大值加1,无需特殊处理空子树,因为空子树的深度0不会影响非空子树的最大值结果

在这里插入图片描述

  • 修改之后AC
class Solution {
public:int minDepth(TreeNode* root) {if(root==nullptr){return 0;}int rightmin=minDepth(root->right);int leftmin=minDepth(root->left);if(leftmin==0){return rightmin+1;}if(rightmin==0){return leftmin+1;}return 1+min(leftmin,rightmin);}
};

222.完全二叉树的结点个数

在这里插入图片描述

算法思路和代码

solution 1

读完这道题我就有思路了,就是把左右子树的分别有多少个节点给他算出来,然后相加不就行了吗 确实能AC,也很简单
但是这题特意说了是完全二叉树的节点 意味着我们其实是可以利用完全二叉树的特性来解决问题的,这才是这题高效率的关键,因此就有了solution2

class Solution {
public:int countNodes(TreeNode* root) {if(root==nullptr){return 0;}int left=countNodes(root->left);int right=countNodes(root->right);int result=left+right+1;return result;}
};
solution 2

在这里插入图片描述
假如你学习过懒猫老师的课程,你一定知道如果给你一个满二叉树的深度k,那么它的节点数就是(2的k次方)-1,而给你一个二叉树的层数x,那么这一层有2的(X-1)次幂 个节点

#include <cmath>class Solution {
public:int countNodes(TreeNode* root) {TreeNode* l = root;TreeNode* r = root;// 记录左、右子树的高度int hl = 0, hr = 0;while (l != nullptr) {l = l->left;hl++;}while (r != nullptr) {r = r->right;hr++;}// 如果左右子树的高度相同,则是一棵满二叉树if (hl == hr) {return (int)pow(2, hl) - 1;}// 如果左右高度不同,则按照普通二叉树的逻辑计算return 1 + countNodes(root->left) + countNodes(root->right);}
};

110 平衡二叉树

在这里插入图片描述
一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1

算法思路及代码

solution

对于这道题其实不用想的太复杂,还是一样,就问你是不是遍历一遍就可以判断是否为二叉平衡树了,怎么判断呢,是不是把两个左右子树高度搞出来,然后比较一下就行了,代码上就是借助一个外部变量然后一个compare函数就行 需要注意的是定义里提到了绝对值,那么最简单能准确表达的 就是借助abs绝对值函数,会用就行

  • 我们需要知道这个compare函数帮我们做了什么事情,需要明确给出这个函数的定义:返回以该节点为根节点的二叉树的高度,如果不是平衡二叉树了则返回-1 不然就很容易被绕进去

  • 明确单层递归的逻辑
    如何判断以当前传入节点为根节点的二叉树是否是平衡二叉树呢?当然是其左子树高度和其右子树高度的差值。

  • 分别求出其左右子树的高度,然后如果差值小于等于1,则返回当前二叉树的高度,否则返回-1,表示已经不是二叉平衡树了。

class Solution {
public:int compare_height(TreeNode* root){if(root==nullptr){return 0;}int left=compare_height(root->left);if(left==-1){return -1;}int right=compare_height(root->right);if(right==-1){return -1;}return abs(left - right) > 1 ? -1 : 1 + max(left, right);}bool isBalanced(TreeNode* root) {int result= compare_height(root);if(result!=-1){return true;}else{return false;}}
};

257 二叉树的所有路径

在这里插入图片描述
这题其实还是有一些不一样 回溯的味道非常明显了 这也是labuladong在它的文章中提到的二叉树与动态规划和回溯算法之间的关系

算法思路及代码

在这里插入图片描述

404 左叶子之和

在这里插入图片描述

算法思路与代码

在这里插入图片描述

solution
class Solution {
public:int sumOfLeftLeaves(TreeNode* root) {if(root==nullptr){return 0;}if(root->left==nullptr &&root->right==nullptr)//叶子节点{return 0;}int leftsum=sumOfLeftLeaves(root->left);if(root->left!=nullptr && root->left->left==nullptr && root->left->right==nullptr){leftsum=root->left->val;}int rightsum=sumOfLeftLeaves(root->right);int sum=leftsum+rightsum;return sum;}
};

513 找树左下角的值

在这里插入图片描述

算法思路及代码

solution1 (遍历)

对于这题来说 (详细的过程都在代码随想录)详细版

  • 首先 思路是先用遍历的方式来解决 一个find函数和一个变量depth就能解决问题
  • 其次我们需要确定这个函数的终止条件(递归的出口)是不是当遇到叶子节点的时候就需要更新depth了,用一个result记录此时深度最左边节点的值
  • 再次我们需要给这个find函数一个明确的定义,就是给一个根节点返回,最大深度最左边孩子的值
  • 细节方面 由于求得是最大深度 我们的depth是需要不断更新的 就是说我们需要一个全局变量保存depth的值 由于我们需要再往下遍历之前记录此时的深度 所以还是采用前序遍历
  • 先给MAXdepth先定义成最小的整型,确保MAXdepth能够正常更新 result同理
class Solution {
public:
int Maxdepth=INT_MIN;
int result;void traverse(TreeNode* root,int depth){if(root->left==nullptr&& root->right==nullptr){//如果找到叶子节点更新最大深度if(depth>Maxdepth){Maxdepth=depth;result=root->val;}return;}if(root->left!=nullptr){depth++;//前序位置traverse(root->left,depth);depth--;//后序位置 回溯(在离开节点时维护depth)}if(root->right!=nullptr){depth++;traverse(root->right,depth);depth--;}return;}int findBottomLeftValue(TreeNode* root) {traverse(root,0);return result;}
solution 2(迭代)

我们只需要在层序遍历到最后一层时 记录最后一层的第一个节点即可
利用了C++STL容器和队列先进先出的特点 定义了一个结点类型的队列 用于存储树的节点 弄个node指向每一层的第一个节点,result记录每次node指向的值,直到队空停止循环

class Solution {
public:int findBottomLeftValue(TreeNode* root) {queue<TreeNode*> que;if (root != NULL) que.push(root);int result = 0;while (!que.empty()) {int size = que.size();for (int i = 0; i < size; i++) {TreeNode* node = que.front();que.pop();if (i == 0) result = node->val; // 记录最后一行第一个元素if (node->left) que.push(node->left);if (node->right) que.push(node->right);}}return result;}
};

112.路径总和

在这里插入图片描述

算法思路以及代码

思路概述
我们需要判断二叉树中是否存在从根节点到叶子节点的路径,使得路径上所有节点值的和等于给定的目标和 targetSum。我们可以通过递归的方式实现这一点,具体来说,使用前序遍历(根-左-右)来遍历二叉树。
详细步骤
定义 findPath 函数:
参数:
cur:当前处理的节点。
sum:当前路径的累加和。
targetSum:目标和。
返回值:布尔值,表示是否存在从当前节点到叶子节点的路径,路径和等于 targetSum。
检查空节点:
如果当前节点 cur 为空,直接返回 false,因为空节点不可能构成路径。
累加节点值:

将当前节点的值 cur->val 累加到路径和 sum 中。
检查叶子节点:
如果当前节点是叶子节点(即没有左子节点和右子节点),检查路径和 sum 是否等于目标和 targetSum。
如果相等,返回 true,表示找到了满足条件的路径。
否则,返回 false。
递归检查左子树:
递归调用 findPath 处理左子树 cur->left。
如果左子树中存在满足条件的路径,返回 true。
递归检查右子树:
递归调用 findPath 处理右子树 cur->right。
如果右子树中存在满足条件的路径,返回 true。
返回结果:
如果左右子树中都没有找到满足条件的路径,返回 false。

solution 1
暴力

一开始我想用一个vector向量来保存每次递归到叶子节点sum的值 也就是说需要遍历出所有路线,然后在主函数中for循环遍历我们的vector 如果找到了targetsum就return true 找不到就return false

#include <iostream>
#include <vector>using namespace std;// Definition for a binary tree node.
struct TreeNode {int val;TreeNode *left;TreeNode *right;TreeNode() : val(0), left(nullptr), right(nullptr) {}TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};class Solution {
public:// 辅助函数,用于递归查找路径和并存储在 record 向量中void findPath(TreeNode* cur, int sum, int targetSum, vector<int>& record) {// 如果当前节点为空,直接返回if (cur == nullptr) {return;}// 累加当前节点的值到路径和sum += cur->val;// 检查当前节点是否是叶子节点(即没有左子节点和右子节点)if (cur->left == nullptr && cur->right == nullptr) {// 如果是叶子节点,将路径和添加到 record 向量中record.push_back(sum);return;}// 递归检查左子树findPath(cur->left, sum, targetSum, record);// 递归检查右子树findPath(cur->right, sum, targetSum, record);}// 主函数,判断是否存在路径和等于目标和bool hasPathSum(TreeNode* root, int targetSum) {vector<int> record; // 用于存储每条路径的和findPath(root, 0, targetSum, record); // 调用辅助函数 findPath// 遍历 record 向量,检查是否存在等于 targetSum 的路径和for (int sum : record) {if (sum == targetSum) {return true;}}// 如果没有找到等于 targetSum 的路径和,返回 falsereturn false;}
};

在这里插入图片描述

但是这个思路确实是太麻烦了是可以去优化的
在这里插入图片描述

class Solution {
public:// 辅助函数,用于递归查找路径和bool findPath(TreeNode* cur, int sum, int targetSum) {// 如果当前节点为空,直接返回 falseif (cur == nullptr) {return false;}// 累加当前节点的值到路径和sum += cur->val;// 检查当前节点是否是叶子节点(即没有左子节点和右子节点)if (cur->left == nullptr && cur->right == nullptr) {// 如果路径和等于目标和,返回 truereturn sum == targetSum;}// 递归检查左子树,如果左子树中存在满足条件的路径,返回 trueif (findPath(cur->left, sum, targetSum)) {return true;}// 递归检查右子树,如果右子树中存在满足条件的路径,返回 trueif (findPath(cur->right, sum, targetSum)) {return true;}// 如果左右子树中都没有找到满足条件的路径,返回 falsereturn false;}// 主函数,判断是否存在路径和等于目标和bool hasPathSum(TreeNode* root, int targetSum) {// 调用辅助函数 findPath 从根节点开始查找return findPath(root, 0, targetSum);}
};

在这里插入图片描述

solution 2

其实跟方法一在思路上本质上是一样的

class Solution 2 {
public:// 辅助函数,用于递归查找路径和bool findPath(TreeNode* cur, int sum, int targetSum) {// 如果当前节点为空,直接返回 falseif (cur == nullptr) {return false;}// 累加当前节点的值到路径和sum += cur->val;// 检查当前节点是否是叶子节点(即没有左子节点和右子节点)if (cur->left == nullptr && cur->right == nullptr) {// 如果路径和等于目标和,返回 truereturn sum == targetSum;}// 递归检查左子树,如果左子树中存在满足条件的路径,返回 trueif (findPath(cur->left, sum, targetSum)) {return true;}// 递归检查右子树,如果右子树中存在满足条件的路径,返回 trueif (findPath(cur->right, sum, targetSum)) {return true;}// 如果左右子树中都没有找到满足条件的路径,返回 falsereturn false;}// 主函数,判断是否存在路径和等于目标和bool hasPathSum(TreeNode* root, int targetSum) {// 调用辅助函数 findPath 从根节点开始查找return findPath(root, 0, targetSum);}
};

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

在这里插入图片描述

我觉得一上来就做这题以及后面的同类型题目其实还是比较困难的 因为我们一开始在学习二叉树的前中后序遍历时一般都是给我们一颗二叉树 让我们给出它的前中后序的遍历序列 这是一个正向的过程 而本题刚刚好是不是完全相反了啊 所以比较难
希望大家在做这题之前去看一下懒猫老师的视频 如果不明白C++ STL的map以及它的基本操作 也需要去了解一下

懒猫老师-数据结构-(29)根据二叉树的遍历结果确定二叉树
如果你看完这个视频 相信你一定对于这题有了最基本的认知,只不过对于代码实现还是不太清楚
也就是说 你知道给你一个前序和中序/后序遍历序列,能确定唯一的二叉树 你在看后面的题目 给你一个前序后序 题目只要你返回任意一颗满足的二叉树即可 这就是原因所在
其实我觉得区别这些的关键其实就是对于每颗子树的根节点怎么去找

思路及代码

主函数是buildTree 辅助函数是build 哈希表inpos的作用就是存储中序遍历的值与其位置的映射比如说:后序遍历的最后一个元素是根节点 我们现在想确定它的左右子树 那么我们只需要找到这个根节点在中序遍历上的位置即可 左边就是左子树 右边就是右子树

solution

il ,ir分别表示前序遍历序列的左右两端 pl,pr分别表示后序遍历的左右两端(我用AI补全了注释方便理解)
如果还有不懂的地方直接看视频就好 我附在这里 真的是讲的非常好
106. 从中序与后序遍历序列构造二叉树 | 手写图解版思路 + 代码讲解


// Solution类用于根据中序和后序遍历数组构建二叉树
class Solution {// 使用哈希表存储中序遍历中每个值的位置,以便快速查找根节点在中序遍历中的位置unordered_map<int,int>inpos;
public:/*** 主要的构建函数,接收中序和后序遍历数组作为输入* @param inorder 中序遍历数组* @param postorder 后序遍历数组* @return 返回构建的二叉树根节点*/TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {int n=inorder.size();// 遍历中序遍历数组,记录每个值的位置for(int i=0;i<n;i++){inpos[inorder[i]]=i;}// 调用build函数开始构建二叉树return build(inorder,postorder,0,n-1,0,n-1);}/*** 递归构建二叉树的辅助函数* @param inorder 中序遍历数组* @param postorder 后序遍历数组* @param il 中序遍历的左边界* @param ir 中序遍历的右边界* @param pl 后序遍历的左边界* @param pr 后序遍历的右边界* @return 返回当前递归构建的子树根节点*/TreeNode* build(vector<int>& inorder,vector<int>& postorder,int il,int ir,int pl,int pr){// 如果中序遍历的左边界大于右边界,说明已经没有节点,返回空指针if(il>ir || pl>pr){return nullptr;}// 计算左子树的节点个数int k=inpos[postorder[pr]]-il;// 获取当前子树的根节点值int rootval=postorder[pr];// 创建根节点TreeNode* root =new TreeNode(rootval);// 递归构建左子树root->left=build(inorder,postorder,il,il+k-1,pl,pl+k-1);// 递归构建右子树root->right=build(inorder,postorder,il+k+1,ir,pl+k,pr-1);// 返回构建的根节点return root;}
};
相关题目

前序中序

TreeNode* constructMaximumBinaryTree([3,2,1,6,0,5]) {// 找到数组中的最大值TreeNode* root = new TreeNode(6);// 递归调用构造左右子树root->left = constructMaximumBinaryTree({3,2,1});root->right = constructMaximumBinaryTree({0,5});return root;
}// 当前 nums 中的最大值就是根节点,然后根据索引递归调用左右数组构造左右子树即可
// 再详细一点,就是如下伪码
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {if (nums.empty()) return nullptr;// 找到数组中的最大值int maxVal = INT_MIN;int index = 0;for (int i = 0; i < nums.size(); i++) {if (nums[i] > maxVal) {maxVal = nums[i];index = i;}}TreeNode* root = new TreeNode(maxVal);// 递归调用构造左右子树root->left = constructMaximumBinaryTree(nums[0..index-1]);root->right = constructMaximumBinaryTree(nums[index+1..nums.size()-1]);
}

前序后序(不唯一)

class Solution {// 存储 postorder 中值到索引的映射unordered_map<int, int> valToIndex;public:TreeNode* constructFromPrePost(vector<int>& preorder, vector<int>& postorder) {for (int i = 0; i < postorder.size(); i++) {valToIndex[postorder[i]] = i;}return build(preorder, 0, preorder.size() - 1,postorder, 0, postorder.size() - 1);}// 定义:根据 preorder[preStart..preEnd] 和 postorder[postStart..postEnd]// 构建二叉树,并返回根节点。TreeNode* build(vector<int>& preorder, int preStart, int preEnd,vector<int>& postorder, int postStart, int postEnd) {if (preStart > preEnd) {return nullptr;}if (preStart == preEnd) {return new TreeNode(preorder[preStart]);}// root 节点对应的值就是前序遍历数组的第一个元素int rootVal = preorder[preStart];// root.left 的值是前序遍历第二个元素// 通过前序和后序遍历构造二叉树的关键在于通过左子树的根节点// 确定 preorder 和 postorder 中左右子树的元素区间int leftRootVal = preorder[preStart + 1];// leftRootVal 在后序遍历数组中的索引int index = valToIndex[leftRootVal];// 左子树的元素个数int leftSize = index - postStart + 1;// 先构造出当前根节点TreeNode* root = new TreeNode(rootVal);// 递归构造左右子树// 根据左子树的根节点索引和元素个数推导左右子树的索引边界root->left = build(preorder, preStart + 1, preStart + leftSize,postorder, postStart, index);root->right = build(preorder, preStart + leftSize + 1, preEnd,postorder, index + 1, postEnd - 1);return root;}
};

654 最大二叉树

在这里插入图片描述

思路及代码

*每个二叉树节点都可以认为是一棵子树的根节点,对于根节点,首先要做的当然是把想办法把自己先构造出来,然后想办法构造自己的左右子树。

所以,我们要遍历数组把找到最大值 maxVal,从而把根节点 root 做出来,然后对 maxVal 左边的数组和右边的数组进行递归构建,作为 root 的左右子树。*

solution
class Solution1 {
public:TreeNode* constructMaximumBinaryTree(vector<int>& nums) {//递归终止条件:只有一个元素的时候if(nums.size()==1)return new TreeNode (nums[0]);//我们需要记录最大值来输出 以及索引下标来分隔左子树 右子树int maxval=0;int index=0;//遍历整个数组找到最大值for(int i=0;i<nums.size();i++){if(nums[i]>maxval){maxval=nums[i];index=i;//不断更新maxval//直到遍历完整个数组找到最大值}}TreeNode* root=new TreeNode(maxval);//递归创建左子树if(index>0){vector<int>vec(nums.begin(),nums.begin()+index) ;//创建一个数组来存储左子树元素范围root->left=constructMaximumBinaryTree(vec);}//右子树if(index<nums.size()-1)//说明至少还有一个元素{vector<int>vec2(nums.begin()+index+1,nums.end());root->right=constructMaximumBinaryTree(vec2);}return root;}
};
**************************************
class Solution2 {
public:// 主函数TreeNode* constructMaximumBinaryTree(std::vector<int>& nums) {return build(nums, 0, nums.size() - 1);}// 定义:将 nums[lo..hi] 构造成符合条件的树,返回根节点TreeNode* build(std::vector<int>& nums, int lo, int hi) {// base caseif (lo > hi) {return nullptr;}// 找到数组中的最大值和对应的索引int index = -1, maxVal = INT_MIN;for (int i = lo; i <= hi; i++) {if (maxVal < nums[i]) {index = i;maxVal = nums[i];}}TreeNode* root = new TreeNode(maxVal);// 递归调用构造左右子树root->left = build(nums, lo, index - 1);root->right = build(nums, index + 1, hi);return root;}
};

二叉搜索树相关

700.二叉搜索树中的搜索

在这里插入图片描述

算法思路与代码

就是利用二叉搜索树左<根<右的特性 使得我们不用去搜索整颗二叉树去找到target

solution
class Solution {
public:TreeNode* searchBST(TreeNode* root, int val) {if(root==nullptr || root->val==val){return root;}       if(root->val>val){return searchBST(root->left,val);}if(root->val<val){return searchBST(root->right,val);}return nullptr;}
};

相关文章:

代码随想录二叉树篇(含源码)

二叉树与递归 前言226.翻转二叉树算法思路及代码solution 1 用分解问题的思路来解决solution 2 用遍历的思路来解决 101.对称二叉树算法思路及代码solution 104.二叉树的最大深度算法思路及代码solution 1 遍历solution 2 分解问题 111.二叉树的最小深度算法思路及代码solution…...

网络安全检测思路

对于主机的安全检测&#xff0c;我们通常直接采用nmap或者类似软件进行扫描&#xff0c;然后针对主机操作系统及其 开放端口判断主机的安全程度&#xff0c;这当然是一种方法&#xff0c;但这种方法往往失之粗糙&#xff0c;我仔细考虑了一下&#xff0c;觉 得按下面的流程进行…...

ios通过xib创建控件

之前写过ios动态创建控件及添加事件&#xff0c;纯手工代码写控件&#xff0c;虽然比较灵活&#xff0c;但是就是代码量比较多。这次我们通过xib来创建app下载列表项 AppView.xib。一个imageview,一个label,一个button构成 1.创建AppView.xib 2.再创建xib对应的mode&#xff0…...

跟着李沐老师学习深度学习(八)

数值稳定性 模型初始化和激活函数 数值稳定性 神经网络的梯度 考虑如下d层的神经网络&#xff08;t代表层&#xff09; 计算损失 l 关于参数 Wt 的梯度&#xff1a; 这样的矩阵乘法带来的问题&#xff1a; &#xff08;1&#xff09;梯度爆炸 &#xff08;2&#xff09;梯度…...

元宵小花灯

吃完饭散步回来的路上&#xff0c;看到一个小朋友拿着元宵小灯&#xff0c;后面的家长也闲适的哼着歌。 想起前阵子看到说&#xff0c;大人爱看小孩玩&#xff0c;也是共享那份天真快乐吧。 我小时候每年的元宵节&#xff0c;也有自己的小灯&#xff0c;那是九几年&#xff0c…...

算法——搜索算法:原理、类型与实战应用

搜索算法&#xff1a;开启高效信息检索的钥匙 在信息爆炸的时代&#xff0c;搜索算法无疑是计算机科学领域中熠熠生辉的存在&#xff0c;它就像一把神奇的钥匙&#xff0c;为我们打开了高效信息检索的大门。无论是在日常生活中&#xff0c;还是在专业的工作场景里&#xff0c;…...

告别传统测量:三维扫描仪测量工件尺寸

在现代制造业中&#xff0c;精确测量工件尺寸是确保产品质量和生产效率的关键环节。然而&#xff0c;传统测量方法往往存在效率低下、精度不足以及操作复杂等问题&#xff0c;难以满足高精度和复杂形状工件的测量需求。 传统工件尺寸测量主要依赖于卡尺、千分尺、三坐标测量仪…...

win32汇编环境,对话框程序使用跟踪条(滑块)控件示例一

;运行效果 ;win32汇编环境,对话框程序使用跟踪条控件示例一 ;生成2条横的跟踪条,分别设置不同的数值范围,设置不同的进度副度的例子 ;直接抄进RadAsm可编译运行。重要部分加备注。 ;下面为asm文件 ;>>>>>>>>>>>>>>>>>>…...

WordPress 角标插件:20 种渐变色彩搭配,打造专属菜单标识

源码介绍 WordPress 角标插件使用教程 本插件旨在为 WordPress 菜单添加角标&#xff0c;并且支持 20 种不同的角标样式。 使用步骤 您可以在 WordPress 后台的“插件”页面中&#xff0c;找到“WordPress 角标插件”&#xff0c;然后点击激活按钮。您需要进入主题的菜单设置…...

【鸿蒙开发】第二十九章 Stage模型-应用上下文Context、进程、线程

目录 1 Stage模型基本概念 1.1 开发流程 3 应用上下文Context的典型使用场景 3.1 获取应用文件路径 3.2 获取和修改加密分区 3.3 获取本应用中其他Module的Context 3.4 订阅进程内UIAbility生命周期变化 4 进程 4.1 概述 5 线程 5.1 线程类型 5.2 使用EventHub进行线…...

window 安装GitLab服务器笔记

目录 视频&#xff1a; 资源&#xff1a; Linux CeneOS7&#xff1a; VMware&#xff1a; Linux无法安装 yum install vim -y 1.手动创建目录 2.下载repo PS 补充视频不可复制的代码 安装GitLab *修改root用户密码相关&#xff08;我卡在第一步就直接放弃了这个操作&…...

3dgs 2025 学习笔记

CVPR 2024 3D方向总汇包含&#xff08;3DGS、三维重建、深度补全、深度估计、全景定位、表面重建和特征匹配等&#xff09;_cvpr2024-structure-awaresparse-viewx-ray3dreconstr-CSDN博客 https://github.com/apple/ml-hugs 3DGS COLMAP-Free 3D Gaussian Splatting ⭐code &…...

2024.1.2版本Android Studio gradle下载超时问题处理

一、问题背景 在项目的根build.gradle里面配置了以下地址后,依旧下载gradle包失败&#xff0c;平常如果出现第三方库或者gradle下载失败,配置以下地址,一般可以下载成功 maven { url https://maven.aliyun.com/repository/public } maven { url https://maven.aliyun.com/nex…...

ffmpeg学习:ubuntu下编译Android版ffmpeg-kit

文章目录 前言一. 配置环境1.1 虚拟机版本1.2 安装Android环境1.2.1 Android SDK安装1.2.2 Android NDK安装 1.3 编译前的准备工作1.3.1 libtasn1-1安装1.3.2 meson安装1.3.3 harfbuzz下载 二. 编译ffmpeg-kit三. 总结 前言 ffmpeg-kit是一款跨多个平台的&#xff0c;用于在应…...

mydb:TM实现

一、说明 TM就是事务管理&#xff1a;实现对于事务的新增&#xff08;active&#xff09;、事务的状态修改&#xff08;commit、abort&#xff09;、事务的状态判断 二、事务管理 2.1创建xid文件/打开xid文件 创建xid、写一个空的 XID 文件头并创建TM public static Transac…...

神经缩放定律:涌现能力与神经元数量、参数数量、数据集大小以及训练所使用的计算量有关

大语言模型的神经缩放定律 大语言模型(LLMs)在自然语言处理领域取得了显著进展,这很大程度上得益于神经缩放定律。该定律指出,模型的损失与模型规模、数据集大小以及训练所使用的计算量呈幂律关系 ,随着模型参数、数据量等的增加,模型会展现出涌现能力,性能会有质的飞跃…...

Microsoft Porject常用字段描述

点击下载《Microsoft Porject常用字段描述》 1. 前言 Microsoft Project 是项目管理中不可或缺的工具&#xff0c;它通过丰富的列&#xff08;字段&#xff09;帮助项目经理全面跟踪和管理项目的各个方面。这些列名通常以简称的形式出现&#xff0c;如 ACWP、BCWP、BCWS 等&a…...

web前端开发中vscode常用的快捷键

1.快速复制一行 快捷键&#xff1a; shiftalt 下箭头(上箭头) 或者 ctrlc 然后 ctrlv 2.选定多个相同的单词 快捷键&#xff1a; ctrl d 先双击选定一个单词&#xff0c;然后按下 ctrl d 可以往下依次选择相同的单词。 这样同时修改相同的单词 3.全局替换某单词 当我们一个…...

鲲鹏(ARM64)升级GCC

1、下载压缩包 wget http://ftp.gnu.org/gnu/gcc/gcc-9.5.0/gcc-9.5.0.tar.xz2、解压 tar -xvf gcc-9.5.0.tar.xzcd gcc-9.5.03、下载关联软件 ./contrib/download_prerequisites4、新建文件夹 mkdir build && cd build5、配置 ../configure -enable-checkingrelea…...

国产操作系统安装DeepSeek

从年前到现在&#xff0c;DeepSeek这款语言AI模型&#xff0c;一经发布直接在全球爆火&#xff0c;在热搜上更是牢牢占据一席之地。无论是技术大神&#xff0c;还是紧跟潮流的技术小白&#xff0c;都被它强大的自然语言处理能力所吸引。作为国产操作系统的用户&#xff0c;千万…...

JSON解析崩溃原因及解决方案

问题记录&#xff1a; /************************************************| * 描述: 将ID124执行NFC操作-JSON解析为结构体* 函数名: cJSON_ID124_to_struct* 参数[ I]: *json_string 待解析的指针* 参数[II]: *wireless_rxd 结构体指针* 返回: 成功返回0 失…...

Go 并发编程深度指南

Go 并发编程深度指南 Go 语言以其内置的并发原语而闻名&#xff0c;通过 goroutine 和 channel 提供了一种高效、安全的并发编程模型。本文将全面解析 Go 的并发机制及其实际应用。 核心概念&#xff1a;Goroutines 和 Channels 1. Goroutines (协程) Go 的轻量级线程实现&…...

设备驱动与文件系统:05 文件使用磁盘的实现

从文件使用磁盘的实现逻辑分享 我们现在讲第30讲&#xff0c;内容是文件使用磁盘的具体实现&#xff0c;也就是相关代码是如何编写的。上一节我们探讨了如何从字符流位置算出盘块号&#xff0c;这是文件操作磁盘的核心。而这节课&#xff0c;我们将深入研究实现这一核心功能的…...

Python学习(7) ----- Python起源

&#x1f40d;《Python 的诞生》&#xff1a;一段圣诞假期的奇妙冒险 &#x1f4cd;时间&#xff1a;1989 年圣诞节 在荷兰阿姆斯特丹的一个寒冷冬夜&#xff0c;灯光昏黄、窗外飘着雪。一个程序员 Guido van Rossum 正窝在家里度假——没有会议、没有项目、没有 bug&#xf…...

低功耗高安全:蓝牙模块在安防系统中的应用方案

随着物联网(IoT)和智能家居的快速发展&#xff0c;安防行业正迎来前所未有的技术革新。蓝牙模块作为一种低功耗、高稳定性的无线通信技术&#xff0c;凭借其低成本、易部署和智能化管理等优势&#xff0c;在安防领域发挥着越来越重要的作用。本文将探讨蓝牙模块在安防系统中的应…...

QT进阶之路:带命名空间的自定义控件在Qt设计器与qss中的使用技巧

文章目录 0.前言1.带命名空间Qt自定义类在QT设计器中的使用技巧1.1 定义一个带命令空间QLabel自定义类1.2 在QT设计器中引入自定义控件类 2.带命名空间Qt自定义类在qss中的使用技巧2.1 命名空间在 QSS 中的特殊语法2.1 在QSS中定义带命名空间的样式 3.在项目中使用带命名空间的…...

自定义protoc-gen-go生成Go结构体,统一字段命名与JSON标签风格

背景 在日常的 Go 微服务开发中&#xff0c;Protocol Buffers&#xff08;protobuf&#xff09; 是广泛使用的数据交换格式。其配套工具 protoc-gen-go 会根据 .proto 文件生成 Go 结构体代码&#xff0c;但默认生成的字段名、JSON tag 命名风格往往不能满足所有团队或项目的代…...

6.Pandas 数据可视化图-1

第三章 数据可视化 文章目录 目录 第三章 数据可视化 文章目录 前言 一、数据可视化 二、使用步骤 1.pyplot 1.1引入库 1.2 设置汉字字体 1.3 数据准备 1.4 设置索引列 ​编辑 1.5 调用绘图函数 2.使用seaborn绘图 2.1 安装导入seaborn 2.2 设置背景风格 2.3 调用绘图方法 2.…...

Android 集成 Firebase 指南

Firebase 是 Google 提供的一套移动开发平台&#xff0c;包含分析、认证、数据库、消息推送等多种服务。以下是在 Android 应用中集成 Firebase 的详细步骤&#xff1a; 1. 准备工作 安装 Android Studio - 确保使用最新版本 创建或打开 Android 项目 - 项目需要配置正确的包…...

基于安卓的线上考试APP源码数据库文档

摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&#xff0c;使信息存…...