【C++代码】找树左下角的值,路径总和,从中序与后序遍历序列构造二叉树,从前序与中序遍历序列构造二叉树--代码随想录

news2024/9/28 9:26:36

题目:找树左下角的值

  • 给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。假设二叉树中至少有一个节点。
题解
  • 使用 height 记录遍历到的节点的高度,curVal 记录高度在 curHeight 的最左节点的值。在深度优先搜索时,我们先搜索当前节点的左子节点,再搜索当前节点的右子节点,然后判断当前节点的高度 height 是否大于 curHeight,如果是,那么将 curVal 设置为当前结点的值,curHeight 设置为 height。因为我们先遍历左子树,然后再遍历右子树,所以对同一高度的所有节点,最左节点肯定是最先被遍历到的。

  • class Solution {
    public:
        void dfs(TreeNode *root,int height,int &curval,int &curheight){
            if(root==nullptr){
                return;
            }
            height++;
            dfs(root->left,height,curval,curheight);
            dfs(root->right,height,curval,curheight);
            if(height>curheight){
                curheight=height;
                curval=root->val;
            }
        }
        int findBottomLeftValue(TreeNode* root) {
            int curval=0,curheight=0;
            dfs(root,0,curval,curheight);
            return curval;
        }
    };
    
  • 时间复杂度:O(n),其中 nnn 是二叉树的节点数目。需要遍历 n 个节点。空间复杂度:O(n)。递归栈需要占用 O(n) 的空间。

  • 使用广度优先搜索遍历每一层的节点。在遍历一个节点时,需要先把它的非空右子节点放入队列,然后再把它的非空左子节点放入队列,这样才能保证从右到左遍历每一层的节点。广度优先搜索所遍历的最后一个节点的值就是最底层最左边节点的值

  •     int findBottomLeftValue(TreeNode* root) {
            int ret;
            queue<TreeNode*> temp_que;
            temp_que.push(root);
            while(!temp_que.empty()){
                auto p=temp_que.front();
                temp_que.pop();
                if(p->right){
                    temp_que.push(p->right);
                }
                if(p->left){
                    temp_que.push(p->left);
                }
                ret=p->val;
            }
            return ret;
        }
    
  • 时间复杂度:O(n),其中 n 是二叉树的节点数目。空间复杂度:O(n)。如果二叉树是满完全二叉树,那么队列 temp_que 最多保存 ⌈ n 2 ⌉ \big \lceil \dfrac{n}{2} \big \rceil 2n 个节点。

题目:路径总和

  • 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false叶子节点 是指没有子节点的节点。
题解
  • 可以使用深度优先遍历的方式(本题前中后序都可以,无所谓,因为中节点也没有处理逻辑)来遍历二叉树。

    • 确定递归函数的参数和返回类型:参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:

      • 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。

      • 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。

      • 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。

      • 在这里插入图片描述

      • 图中可以看出,遍历的路线,并不要遍历整棵树,所以递归函数需要返回值,可以用bool类型表示。

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

    • 确定单层递归的逻辑:因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回。

    • class Solution {
      public:
          bool traversal(TreeNode* cur,int count){
              if(!cur->left&&!cur->right&&count==0){
                  return true;
              }
              if(!cur->left&&!cur->right){
                  return false;
              }
              if(cur->left){
                  count-=cur->left->val;
                  if(traversal(cur->left,count)){
                      return true;
                  }
                  count+=cur->left->val;
              }
              if(cur->right){
                  count-=cur->right->val;
                  if(traversal(cur->right,count)){
                      return true;
                  }
                  count+=cur->right->val;
              }
              return false;
          }
          bool hasPathSum(TreeNode* root, int targetSum) {
              if(root==nullptr){
                  return false;
              }
              return traversal(root,targetSum-root->val);
          }
      };
      
  • 解法二:首先我们可以想到使用广度优先搜索的方式,记录从根节点到当前节点的路径和,以防止重复计算。这样我们使用两个队列,分别存储将要遍历的节点,以及根节点到这些节点的路径和即可。

    •     bool hasPathSum(TreeNode* root, int targetSum) {
              if(root==nullptr){
                  return false;
              }
              queue<TreeNode*> temp_que_node;
              queue<int> temp_que_int;
              temp_que_node.push(root);
              temp_que_int.push(root->val);
              while(!temp_que_node.empty()){
                  TreeNode *temp_node=temp_que_node.front();
                  int temp_int=temp_que_int.front();
                  temp_que_node.pop();
                  temp_que_int.pop();
                  if(temp_node->left==nullptr&&temp_node->right==nullptr){
                      if(temp_int==targetSum){
                          return true;
                      }
                      continue;
                  }
                  if(temp_node->left!=nullptr){
                      temp_que_node.push(temp_node->left);
                      temp_que_int.push(temp_node->left->val+temp_int);
                  }
                  if(temp_node->right!=nullptr){
                      temp_que_node.push(temp_node->right);
                      temp_que_int.push(temp_node->right->val+temp_int);
                  }
              }
              return false;
          }
      
    • 时间复杂度:O(N),其中 N 是树的节点数。对每个节点访问一次。空间复杂度:O(N),其中 N 是树的节点数。空间复杂度主要取决于队列的开销,队列中的元素个数不会超过树的节点数。

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

  • 给定两个整数数组 inorderpostorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树
题解
  • 以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。

  • 在这里插入图片描述

  • 来看一下一共分几步:

    • 第一步:如果数组大小为零的话,说明是空节点了。
    • 第二步:如果不为空,那么取后序数组最后一个元素作为节点元素
    • 第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点。
    • 第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)。
    • 第五步:切割后序数组,切成后序左数组和后序右数组。
    • 第六步:递归处理左区间和右区间
  • class Solution {
    public:
        TreeNode* traversal(vector<int>& inorder,vector<int>& postorder){
            if(postorder.size()==0){
                return nullptr;
            }
            int rootval=postorder[postorder.size()-1];
            TreeNode* root=new TreeNode(rootval);
            if(postorder.size()==1){
                return root;
            }
            int delimiterindex;
            for(delimiterindex=0;delimiterindex<inorder.size();delimiterindex++){
                if(inorder[delimiterindex]==rootval){
                    break;
                }
            }
            vector<int> leftinorder(inorder.begin(),inorder.begin()+delimiterindex);
            vector<int> rightinorder(inorder.begin()+1+delimiterindex,inorder.end());
            postorder.resize(postorder.size()-1);
            vector<int> leftpostorder(postorder.begin(),postorder.begin()+leftinorder.size());
            vector<int> rightpostorder(postorder.begin()+leftinorder.size(),postorder.end());
            root->left=traversal(leftinorder,leftpostorder);
            root->right=traversal(rightinorder,rightpostorder);
            return root;
        }
        TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
            if(inorder.size()==0||postorder.size()==0)
            return nullptr;
            return traversal(inorder,postorder);
        }
    };
    
  • 首先解决这道题我们需要明确给定一棵二叉树,我们是如何对其进行中序遍历与后序遍历的:

    • 中序遍历的顺序是每次遍历左孩子,再遍历根节点,最后遍历右孩子。

    • 后序遍历的顺序是每次遍历左孩子,再遍历右孩子,最后遍历根节点。

    •     void inorder_fun(TreeNode* node){//中序遍历
              if(root==nullptr){
                  return;
              }
              inorder_fun(root->left);
              queue<TreeNode*> temp_que;
              temp_que.push_back(root->val);
              inorder_fun(root->right);
          }
          void postorder_fun(TreeNode* node){//后序遍历
              if(root==nullptr){
                  return;
              }
              postorder_fun(root->left);
              postorder_fun(root->right);
              queue<TreeNode*> temp_que;
              temp_que.push_back(temp_que);
          }
      
  • 因此根据上文所述,我们可以发现后序遍历的数组最后一个元素代表的即为根节点。知道这个性质后,我们可以利用已知的根节点信息在中序遍历的数组中找到根节点所在的下标,然后根据其将中序遍历的数组分成左右两部分,左边部分即左子树,右边部分为右子树,针对每个部分可以用同样的方法继续递归下去构造。

  • 为了高效查找根节点元素在中序遍历数组中的下标,我们选择创建哈希表来存储中序序列,即建立一个(元素,下标)键值对的哈希表。定义递归函数 helper(in_left, in_right) 表示当前递归到中序序列中当前子树的左右边界,递归入口为helper(0, n - 1)

    • 如果 in_left > in_right,说明子树为空,返回空节点。

    • 选择后序遍历的最后一个节点作为根节点。

    • 利用哈希表 O(1) 查询当根节点在中序遍历中下标为 index。从 in_left 到 index - 1 属于左子树,从 index + 1 到 in_right 属于右子树。

    • 根据后序遍历逻辑,递归创建右子树 和左子树 。 注意这里有需要先创建右子树,再创建左子树的依赖关系。 可以理解为在后序遍历的数组中整个数组是先存储左子树的节点,再存储右子树的节点,最后存储根节点,如果按每次选择「后序遍历的最后一个节点」为根节点,则先被构造出来的应该为右子树。helper(index + 1, in_right);helper(in_left, index - 1)。

    • 返回根节点 root

    • class Solution {
          int post_index;
          unordered_map<int,int> idx_map;
      public:
          TreeNode* helper(int in_left,int in_right,vector<int>& inorder,vector<int>& postorder){
              if(in_left>in_right){
                  return nullptr;
              }
              int rootval=postorder[post_index];
              TreeNode* root = new TreeNode(rootval);
              int index=idx_map[rootval];// 根据 root 所在位置分成左右两棵子树
              post_index--;
              root->right=helper(index+1,in_right,inorder,postorder);
              root->left=helper(in_left,index-1,inorder,postorder);
              return root;
          }
          TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
              post_index=postorder.size()-1;
              int idx=0;
              for(auto &val:inorder){
                  idx_map[val]=idx++;
              }
              return helper(0,(int)inorder.size()-1,inorder,postorder);
          }
      };
      
    • 时间复杂度:O(n),其中 n 是树中的节点个数。空间复杂度:O(n)。我们需要使用 O(n) 的空间存储哈希表,以及 O(h)(其中 h 是树的高度)的空间表示递归时栈空间。这里 h<n,所以总空间复杂度为 O(n)。

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

  • 给定两个整数数组 preorderinorder ,其中 preorder 是二叉树的先序遍历inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
题解
  • 前序和中序可以唯一确定一棵二叉树。后序和中序可以唯一确定一棵二叉树。前序和后序不能唯一确定一棵二叉树!,因为没有中序遍历无法确定左右部分,也就是无法分割。

  • 二叉树前序遍历的顺序为:先遍历根节点;随后递归地遍历左子树;最后递归地遍历右子树。[ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]

  • 二叉树中序遍历的顺序为:先递归地遍历左子树;随后遍历根节点;最后递归地遍历右子树。[ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]

  • 在「递归」地遍历某个子树的过程中,我们也是将这颗子树看成一颗全新的树,按照上述的顺序进行遍历。挖掘「前序遍历」和「中序遍历」的性质,我们就可以得出本题的做法。

  • 只要我们在中序遍历中定位到根节点,那么我们就可以分别知道左子树和右子树中的节点数目。由于同一颗子树的前序遍历和中序遍历的长度显然是相同的,因此我们就可以对应到前序遍历的结果中,对上述形式中的所有左右括号进行定位。

  • 这样以来,我们就知道了左子树的前序遍历和中序遍历结果,以及右子树的前序遍历和中序遍历结果,我们就可以递归地对构造出左子树和右子树,再将这两颗子树接到根节点的左右位置。

  • 在中序遍历中对根节点进行定位时,一种简单的方法是直接扫描整个中序遍历的结果并找出根节点,但这样做的时间复杂度较高。我们可以考虑使用哈希表来帮助我们快速地定位根节点。对于哈希映射中的每个键值对,键表示一个元素(节点的值),值表示其在中序遍历中的出现位置。在构造二叉树的过程之前,我们可以对中序遍历的列表进行一遍扫描,就可以构造出这个哈希映射。在此后构造二叉树的过程中,我们就只需要 O(1)O(1)O(1) 的时间对根节点进行定位了。

  • /**
     * 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 {
        unordered_map<int,int> index;
    public:
        TreeNode* mytree(const vector<int>& preorder,const vector<int>& inorder,int preorder_left,int preorder_right,int inorder_left,int inorder_right){
            if(preorder_left>preorder_right){
                return nullptr;
            }
            // 前序遍历中的第一个节点就是根节点
            int preorder_root=preorder_left;
            // 在中序遍历中定位根节点
            int inorder_root=index[preorder[preorder_root]];
            // 先把根节点建立出来
            TreeNode* root=new TreeNode(preorder[preorder_root]);
            int size_lefttree=inorder_root-inorder_left;
            // 递归地构造左子树,并连接到根节点
            // 先序遍历中「从 左边界+1 开始的 size_lefttree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
            root->left=mytree(preorder,inorder,preorder_left+1,preorder_left+size_lefttree,inorder_left,inorder_root-1);
            // 递归地构造右子树,并连接到根节点
            // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
            root->right=mytree(preorder,inorder,preorder_left+size_lefttree+1,preorder_right,inorder_root+1,inorder_right);
            return root;
        }
        TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
            int n=preorder.size();
            for(int i=0;i<n;i++){
                index[inorder[i]]=i;
            }
            return mytree(preorder,inorder,0,n-1,0,n-1);
        }
    };
    
    • 时间复杂度:O(n),其中 n 是树中的节点个数。空间复杂度:O(n),除去返回的答案需要的 O(n) 空间之外,我们还需要使用 O(n) 的空间存储哈希映射,以及 O(h)(其中 h 是树的高度)的空间表示递归时栈空间。这里 h<n,所以总空间复杂度为 O(n)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1035084.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

获取文件最后修改时间

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl Java源码 public void testGetFileTime() {try {String string "E://test.txt";File file new File(string);Path path file.toPath();BasicFileAttributes ba…

第P6周—好莱坞明星识别(2)

五、模型训练 # 训练循环def train(dataloader, model, loss_fn, optimizer):size len(dataloader.dataset) # 训练集的大小num_batches len(dataloader) # 批次数目train_loss, train_acc 0, 0 # 初始化训练损失和正确率for X, y in dataloader: # 获取图片及其标签X…

定制化精准推送与用户分组策略:数智营销的硬技能

对于移动应用开发者和运营者而言&#xff0c;推送是保持良好客户互动&#xff0c;实现用户裂变增长的重要方式&#xff0c;在实际推送服务设计中&#xff0c;往往会根据不同的需求和应用场景&#xff0c;针对性的选取特定对象发送特定内容的推送。具体而言&#xff0c;主流的智…

【C刷题】day3

一、选择题 1、已知函数的原型是&#xff1a; int fun(char b[10], int *a); &#xff0c;设定义&#xff1a; char c[10];int d; &#xff0c;正确的调用语句是&#xff08; &#xff09; A: fun(c,&d); B: fun(c,d); C: fun(&c,&d); D: fun(&c,d); 【答案…

大模型应用发展的方向|代理 Agent 的兴起及其未来(下)

“ 借助LLM作为代理大脑的优势&#xff0c;探讨了单一代理、多代理系统和人机协作等应用场景&#xff0c;探讨了代理的社会行为、心理活动以及在模拟社会环境中观察新兴社会现象和人类洞见的可能性。” 01 — 造福人类&#xff1a;代理实践 LLM型智能代理是一种新兴的方向&…

【51单片机实验笔记】LED篇(二)多色LED的基本控制

目录 前言硬件介绍双色LED三色LED七彩自动闪烁LED 接线图面包板介绍直插电阻介绍色环解析 双色LED实际接线图三色LED实际接线图七彩自动闪烁LED实际接线图 软件实现双色LED交替闪烁三色LED灯交替闪烁 总结 前言 本节内容我们学习了解一些多色LED的显示原理及驱动方式。 本节涉…

Pytorch---空间特征金字塔SPP模块的实现

文章目录 一、SPP模块二、使用pytorch实现 一、SPP模块 SPP模块是指定空间特征金字塔模块&#xff0c;是由何凯明在2014年的论文中所提出的。 论文地址如下&#xff1a; 论文地址 该模块的主要作用是&#xff1a;在分类网络中&#xff0c;通过分类器之后&#xff0c;与全连接…

stack与queue的简单封装

前言&#xff1a; stack与queue即栈和队列&#xff0c;先进后出/先进先出的特性我们早已了然于心&#xff0c; 在学习数据结构时&#xff0c;我们利用c语言实现栈与队列&#xff0c;从结构体写起&#xff0c;利用数组或指针表示他们的数据成员&#xff0c;之后再一个个实现他们…

Linux 故障定位手段之保存某个时间段内的top结果

在Linux中对故障原因进行定位时&#xff0c;除了查看对应的软体运行日志、OS运行日之外&#xff0c;还可以查看 top 的资源消耗结果。 参考语句&#xff1a; 以CPU为基准列进行排序记录TOP结果 nohup top -d 1 -b -o %CPU | tee -a /tmp/cpu.txt & 语句含义&#xff1a;每…

etc目录下的profile.d文件目录设置环境变量和全局脚本

一、设置环境变量 etc目录下的profile.d文件目录 /etc/profile.d 1、编写 vi test.sh文件内容 # jdk变量 export ZHK_HOME/root export PATH$PATH:$ZHK_HOME/test # 可以取出来ZHK_HOME变量给ZZZ_HOME赋值 export ZZZ_HOME${ZHK_HOME}/test2、刷新 执行source /etc/profile …

34.CSS魔线图标的悬停效果

效果 源码 index.html <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Icon Fill Hover Effects</title> <link rel="stylesheet" h…

【深度学习实验】前馈神经网络(final):final

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入必要的工具包 1. 构建数据集&#xff08;IrisDataset&#xff09; 2. 构建模型&#xff08;FeedForward&#xff09; a. __init__(初始化) b. forward(前向传播) 3.整合训练、评估…

Mojo:新型AI语言中的7个令人惊叹的Python升级,用简单的英语解释人工智能

Mojo&#xff1a;新型AI语言中的7个令人惊叹的Python升级 编程之美 用简单的英语解释人工智能 编程之美 由Coding Beauty设计的图像&#xff0c;使用Mojo标志和Python标志。 它比C更快&#xff0c;与Python一样简单&#xff0c;但速度提高了35000倍。 进入Mojo&#xff1a;一种…

冒泡排序与选择排序(最low的两兄弟)

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 前言&#xff1a; 在我们的生活中&#xff0c;无处不在用到排序&#xff0c;比如说成绩的排名&#xff0c;淘宝&#xff0c;京东等等商品在各个方面的排序&#xff0c;这样看来一个好的算 法很重要&#xff0c;接下来我们要先…

深度学习自学笔记四:浅层神经网络(一)

一、神经网络概述 神经网络是一种模仿人脑神经系统结构和功能的计算模型。它由大量相互连接的人工神经元组成&#xff0c;并通过这些神经元之间的信息传递来进行计算和学习。 神经网络的基本组成单元是神经元&#xff0c;也称为节点或单元。每个神经元接收来自其他神经元的输…

第一百五十二回 自定义组件综合实例:游戏摇杆三

文章目录 内容回顾优化性能示例代码我们在上一章回中介绍了 如何实现游戏摇杆相关的内容,本章回中将继续介绍这方面的知识.闲话休提,让我们一起Talk Flutter吧。 内容回顾 我们在前面章回中介绍了游戏摇杆的概念以及实现方法,并且通过示例代码演示了实现游戏摇杆的整个过程…

取消github向邮箱推送邮件及修改密码

取消或者说禁止github向邮箱推送邮件&#xff0c;因为量太大了&#xff0c;没多久就上万封邮件&#xff0c;于是取消订阅或者推送。 1、登录github 2、点击右上角头像&#xff0c;然后点击Settings 3、点击Notifications&#xff08;通知&#xff09; 4、取消各种推送&#x…

SpringBoot开发实战(微课视频版)

ISBN: 978-7-302-52819-7 编著&#xff1a;吴胜 页数&#xff1a;311页 阅读时间&#xff1a;2023-06-24 推荐指数&#xff1a;★★★★☆ 本文介绍SpringBoot 2.0.5 、JDK 1.8&#xff0c;虽然现在已经不维护了&#xff0c;但是大体的流程还是对口的&#xff0c; 而且书里面讲…

新手学习:ArcGIS对shp文件裁剪

新手学习&#xff1a;ArcGIS对SHP文件裁剪 新手学习 记录每个步骤&#xff0c;因为有很多控件可能刚开始还不熟悉&#xff0c;根本不知道在哪里&#xff0c;所以写的比较详细。 1.添加要裁剪的shp文件 2.查看shp文件的地理坐标系 双击shp文件&#xff0c;就可以查看shp文件的…

LeetCode【174. 地下城游戏】

一片丹心图报国&#xff0c;两行清泪为忠家。——于谦 恶魔们抓住了公主并将她关在了地下城 dungeon 的 右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里&#xff0c;他必须穿过地下城并通过对抗恶魔来拯救公主。 骑士的初始健康…