LeetCode刷题day39

简介: LeetCode刷题day39

今日刷题重点—构造二叉树

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

根据一棵树的中序遍历与后序遍历构造二叉树。

注意:

你可以假设树中没有重复的元素。

例如,给出

中序遍历 inorder = [9,3,15,20,7]

后序遍历 postorder = [9,15,7,20,3]

返回如下的二叉树:

    3
   / \
  9  20
    /  \
   15   7

思路分析

中序和后序构建树的思路:以后序最后一个节点为分割点,对中序进行分割,分割成左中序和右中序;之后根据左中序和右中序的长度再对后序进行分割,分割成左后序和右后序

图解如下:


c1e1e2a75e6f456ead741319b13993ea.png

判断后序数组是否为0,如果为0则说明是空节点了.

如果不为0,取后序元素的最后一个元素为根构建子树,同时以该元素为分割点.

根据切割点切割中序数组,把中序数组分成左中序和右中序.

根据分成的左右中序的长度把后序数组分成左后序和右后序.

传入左中序和左后序递归构建子树的左子树

传入右中序和右后序递归构建子树的右子树

注意点:无论是哪种方法,都要对递归时数组范围进行确定,如左闭右开,这个原则一定要保持不变.


参考代码


// 方法一:参数传递为数组 
TreeNode* traversal(vector<int>& inorder,vector<int>& postorder) {
  if(postorder.size()==0) {
    return NULL;
  }
  //获取当前的中间节点--对应后序数组的最后一个
  int rootValue = postorder[postorder.size()-1];
  //以该节点为根创建新的子树
  TreeNode* root = new TreeNode(rootValue);
  // 找到中序遍历的切割点.
  int delimiterIndex;
  for(delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
    if(inorder[delimiterIndex]==rootValue) {
      break;
    }
  }
  //切割中序数组
  //左中序左闭右开区间 [0,delimiterIndex)
  vector<int> leftInorder(inorder.begin(),inorder.begin()+delimiterIndex);
  //右中序 [delimiterIndex+1,end)
  vector<int> rightInorder(inorder.begin()+delimiterIndex+1,inorder.end());
  postorder.resize(postorder.size()-1);
  //切割后序数组 根据上面两个中序数组大小进行切割
  //左后序 [0,leftorder.size)
  vector<int> leftPostorder(postorder.begin(),postorder.begin()+leftInorder.size());
  //右后序[leftorder.size(),end)
  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() || !postorder.size()) {
    return NULL;
  }
  return traversal(inorder,postorder);
}

参考代码2

以上代码在每次递归时都需要新创建数组,并且参数传递的都是数组.其实每次只需要把需要递归的数组范围下标传入就可.

//参数传递为下标. 
TreeNode* traversal(vector<int>& inorder,int inorderBegin,int inorderEnd, vector<int>& postorder,int postorderBegin,int postorderEnd) {
  if(postorderBegin==postorderEnd){
    return NULL;
  }
  //获取当前的中间节点--对应后序数组的最后一个
  int rootValue = postorder[postorderEnd-1];
  //以该节点为根创建新的子树
  TreeNode* root = new TreeNode(rootValue);
  // 找到中序遍历的切割点.
  int delimiterIndex;
  for(delimiterIndex = inorderBegin; delimiterIndex < inorderEnd; delimiterIndex++) {
    if(inorder[delimiterIndex]==rootValue) {
      break;
    }
  }
  //切割中序数组
  //左中序左闭右开区间 [leftInorderBegin,leftInorderEnd)
  int leftInorderBegin = inorderBegin;
  int leftInorderEnd = delimiterIndex;
  //右中序 [rightInorderBegin+1,end)
  int rightInorderBegin = delimiterIndex + 1;
  int rightInorderEnd = inorderEnd;
  //切割后序数组 根据上面两个中序数组大小进行切割
  //左后序 [leftPostorderBegin,leftPostorderEnd)
  int leftPostorderBegin = postorderBegin;
  int leftPostorderEnd = postorderBegin+delimiterIndex-inorderBegin;//终止位置 
  //右后序[leftorder.size(),end)
  int rightPostorderBegin = postorderBegin+delimiterIndex-inorderBegin;
  int rightPostorderEnd =  postorderEnd-1;//排除最后一个元素,已经作为节点了 
  //进行递归 创建其左右子树.
  root->left =  traversal(inorder,leftInorderBegin,leftInorderEnd,postorder,leftPostorderBegin,leftPostorderEnd);
  root->right = traversal(inorder,rightInorderBegin,rightInorderEnd,postorder,rightPostorderBegin,rightPostorderEnd);
  return root;
}
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
  if(!inorder.size() || !postorder.size()) {
    return NULL;
  }
  return traversal(inorder,0,inorder.size(),postorder,0,postorder.size());
}

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

给定一棵树的前序遍历 preorder 与中序遍历 inorder。请构造二叉树并返回其根节点。

示例 1:

f210adf3d60f4e91855b2084c1ce0291.jpg

Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]

示例 2:

Input: preorder = [-1], inorder = [-1]
Output: [-1]

思路分析

思路和步骤和上面那种方法基本相同.

参考代码1

TreeNode* traversal(vector<int>& preorder, vector<int>& inorder){
  if(preorder.size()==0){
    return NULL;
  }
  int rootValue = preorder[0];
  TreeNode* root = new TreeNode(rootValue);
  //寻找切割点
  int delimiterIndex; 
  for(delimiterIndex = 0;delimiterIndex < inorder.size();delimiterIndex++){
    if(inorder[delimiterIndex] == rootValue){
      break;
    }
  }
//  cout<<"delimiterIndex:"<<delimiterIndex<<endl;
  //分割中序序列. 
  //左中序 左闭右开[0,delimiterIndex) 
  vector<int> leftInorder(inorder.begin(),inorder.begin()+delimiterIndex);
  //右中序   [delimiterIndex+1,end) 
  vector<int> rightInorder(inorder.begin()+delimiterIndex+1,inorder.end());
  //分割先序序列
  //左先序 [preorder.begin()+1)
  vector<int> leftPreorder(preorder.begin()+1,preorder.begin()+1+leftInorder.size()); 
  //右先序 
  vector<int> rightPreorder(preorder.begin()+1+leftInorder.size(),preorder.end());
  root->left = traversal(leftPreorder,leftInorder);
  root->right = traversal(rightPreorder,rightInorder);
  return root;  
} 
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
  if(preorder.size()==0||inorder.size()==0){
    return NULL;
  }
  return traversal(preorder,inorder);
}

参考代码2

TreeNode* traversal(vector<int>& preorder,int preorderBegin,int preorderEnd, vector<int>& inorder,int inorderBegin,int inorderEnd){
  if(preorderBegin==preorderEnd){
    return NULL;
  }
  int rootValue = preorder[preorderBegin];
  TreeNode* root = new TreeNode(rootValue);
  //寻找切割点
  int delimiterIndex;
  for(delimiterIndex = inorderBegin;delimiterIndex < inorderEnd;delimiterIndex++){
    if(inorder[delimiterIndex]==rootValue){
      break;
    } 
  } 
  //分割中序序列. 
  //左中序 [leftInorderBegin,leftInorderEnd) 
  int leftInorderBegin = inorderBegin;
  int leftInorderEnd = delimiterIndex;
  //右中序 [rightInorderBegin,rightInorderEnd) 
  int rightInorderBegin = delimiterIndex + 1;
  int rightInorderEnd = inorderEnd;
  //分割前序序列
  //左前序[leftPreorderBegin,leftPreorderEnd) 
  int leftPreorderBegin = preorderBegin + 1;
  int leftPreorderEnd = preorderBegin+1+leftInorderEnd-leftInorderBegin;
  //右先序 [rightPreorderBegin,rightPreorderEnd) 
  int rightPreorderBegin = preorderBegin+1+leftInorderEnd-leftInorderBegin;
  int rightPreorderEnd = preorderEnd;
  root->left = traversal(preorder,leftPreorderBegin,leftPreorderEnd,inorder,leftInorderBegin,leftInorderEnd);
  root->right = traversal(preorder,rightPreorderBegin,rightPreorderEnd,inorder,rightInorderBegin,rightInorderEnd);
  return root;  
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
  if(preorder.size()==0||inorder.size()==0){
    return NULL;
  }
  return traversal(preorder,0,preorder.size(),inorder,0,inorder.size());
}

UVA536 二叉树重建 Tree Recovery

输入一棵二叉树的先序遍历和中序遍历序列,输出它的后序遍历序列。

样例1

DBACEGF ABCDEFG
BCAD CBAD

样例2

ACBFGED
CDAB

思路分析

由于我们只是要求根据先序和中序输出后序,所以可以边递归边输出.

参考代码1—参数传递为字符串

#include<bits/stdc++.h>
using namespace std;
string preorder,inorder;
void postorder(string pre,string in)
{
  if(pre.size()<=0)
    return;
  int len=0;
//    while(in[len]!=pre[0])//在中序中找到的根结点的位置 
//    {
//        len++;
//    }
  len=in.find(pre[0]);//返回其位置 
  postorder(pre.substr(1,len),in.substr(0,len));
  postorder(pre.substr(len+1),in.substr(len+1));
  cout<<pre[0];
}
int main()
{
  while(cin>>preorder>>inorder)
  {
    postorder(preorder,inorder);
    //cout<<inorder.find(preorder[0]);
    cout<<endl;
  }
  return 0;
 } 

参考代码2—参数传递为下标

#include<bits/stdc++.h>
using namespace std;
string preorder,inorder;
void postorder(int l1,int l2,int n)
{
  if(n<=0)
    return;
  int len=inorder.find(preorder[l1])-l2;//返回其位置 
  postorder(l1+1,l2,len);
  postorder(l1+len+1,l2+len+1,n-len-1);
  cout<<preorder[l1];
}
int main()
{
  while(cin>>preorder>>inorder)
  {
    int len=preorder.size();
    postorder(0,0,len);
    cout<<endl;
  }
  return 0;
 } 

654. 最大二叉树

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

二叉树的根是数组 nums 中的最大元素。

左子树是通过数组中 最大值左边部分 递归构造出的最大二叉树。

右子树是通过数组中 最大值右边部分 递归构造出的最大二叉树。

返回有给定数组 nums 构建的 最大二叉树 。

示例 1:

1066f1a1695f451f95f9d3227ddcfb53.jpg

输入:nums = [3,2,1,6,0,5]
输出:[6,3,5,null,2,0,null,null,1]

解释:递归调用如下所示:


[3,2,1,6,0,5] 中的最大值是 6 ,左边部分是 [3,2,1] ,右边部分是 [0,5] 。

[3,2,1] 中的最大值是 3 ,左边部分是 [] ,右边部分是 [2,1] 。

空数组,无子节点。

[2,1] 中的最大值是 2 ,左边部分是 [] ,右边部分是 [1] 。

空数组,无子节点。

只有一个元素,所以子节点是一个值为 1 的节点。

[0,5] 中的最大值是 5 ,左边部分是 [0] ,右边部分是 [] 。

只有一个元素,所以子节点是一个值为 0 的节点。

空数组,无子节点。

示例 2:

05c69cb1ed7a401d80ce988d96665b1e.jpg

输入:nums = [3,2,1]
输出:[3,null,2,null,1]

思路分析

构造树一般采用先序遍历,先构造根节点再构造左子树,右子树.

图解如下:

3bbe2ff7d28a4c47aa23b560fb290993.gif


递归三要素:


参数和返回值:参数为要构建树的数组,返回值为该子树构建完成后的子树根节点.

确定结束条件:每次当数组只有一个节点时,说明该节点是叶子节点,创建该节点后直接返回即可,无需后序的递归.(当然也可以不用该结束条件,因为递归前有if条件控制,所以暗含有结束条件)

确定单层递归逻辑:先找到数组中最大的元素,以该元素为根创建子树;同时该元素将作为分割点,将数组分为左数组和右数组,然后递归创建左子树和右子树.


参考代码1—参数传递为数组.

TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
//  if(nums.size()==1) { //如果只有一个叶子节点 这个结束条件可加可不加,因为我们在下面构建左子树和右子树时也进行了判断. 
//    node->val = nums[0];
//    return node;
//  }
  //找到数组中最大的值和对应的下标
  int maxValue = 0;
  int maxValueIndex = 0;
  for(int i = 0; i <nums.size(); i++) {
    if(maxValue<nums[i]) {
      maxValue = nums[i];
      maxValueIndex = i;
    }
  }
  TreeNode* node = new TreeNode(maxValue);
  //构造左子树
  if(maxValueIndex) { //当 maxValueIndex = 0,无左子树
    vector<int> newVec(nums.begin(),nums.begin()+maxValueIndex);
    node->left =  constructMaximumBinaryTree(newVec);
  }
  //构造右子树
  if(maxValueIndex<nums.size()-1) { //当 maxValueIndex= nums.size()-1时,无右子树
    vector<int> newVec(nums.begin()+maxValueIndex+1,nums.end());
    node->right = constructMaximumBinaryTree(newVec);
  }
  return node;
}

参考代码2—参数传递为下标

递归三部曲:


确定参数和返回值:参数为原始数组,需要递归的数组的范围下标. 返回值为子树的根节点.

确定结束条件:当left>=right表示当前没有节点传入来构建子树.返回NULL(我们采用的是左闭右开区间)

确定单层递归逻辑:确定最大值,并以该值为根节点创建子树,同时以该节点为分割点,进行左递归和右递归.


TreeNode* traversal(vector<int>& nums,int left,int right) {
  if(left>=right) {
    return nullptr;
  }
  int maxValueIndex = left;
  for(int i = left+1; i< right; i++) {
    if(nums[i]>nums[maxValueIndex]) {
      maxValueIndex = i;
    }
  }
  TreeNode* node = new TreeNode(nums[maxValueIndex]);
  //构建左子树 [left,maxValueIndex)
  node->left = traversal(nums,left,maxValueIndex);
  //构建右子树 (maxValueIndex+1,right]
  node->right = traversal(nums,maxValueIndex+1,right);
  return node;
}
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
  return traversal(nums,0,nums.size());
}
相关文章
|
3月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 32 - III. 从上到下打印二叉树 III
本文介绍了两种Python实现方法,用于按照之字形顺序打印二叉树的层次遍历结果,实现了在奇数层正序、偶数层反序打印节点的功能。
63 6
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 26. 树的子结构
这篇文章提供了解决LeetCode上"剑指Offer 26. 树的子结构"问题的Python代码实现和解析,判断一棵树B是否是另一棵树A的子结构。
56 4
|
4月前
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
128 2
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
280页PDF,全方位评估OpenAI o1,Leetcode刷题准确率竟这么高
【10月更文挑战第24天】近年来,OpenAI的o1模型在大型语言模型(LLMs)中脱颖而出,展现出卓越的推理能力和知识整合能力。基于Transformer架构,o1模型采用了链式思维和强化学习等先进技术,显著提升了其在编程竞赛、医学影像报告生成、数学问题解决、自然语言推理和芯片设计等领域的表现。本文将全面评估o1模型的性能及其对AI研究和应用的潜在影响。
46 1
|
3月前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
4月前
|
索引 Python
【Leetcode刷题Python】从列表list中创建一颗二叉树
本文介绍了如何使用Python递归函数从列表中创建二叉树,其中每个节点的左右子节点索引分别是当前节点索引的2倍加1和2倍加2。
72 7
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 22. 链表中倒数第k个节点
Leetcode题目"剑指 Offer 22. 链表中倒数第k个节点"的Python解决方案,使用双指针法找到并返回链表中倒数第k个节点。
58 5
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 30. 包含min函数的栈
本文提供了实现一个包含min函数的栈的Python代码,确保min、push和pop操作的时间复杂度为O(1)。
34 4
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
Leetcode题目"剑指 Offer 21. 调整数组顺序使奇数位于偶数前面"的两种Python解决方案,一种是使用双端队列调整数组顺序,另一种是使用双指针法将奇数移到数组前半部分,偶数移到后半部分。
29 4