输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 例如,给出 前序遍历 preorder = [3,9,20,15,7] 中序遍历 inorder = [9,3,15,20,7] 返回如下的二叉树: 3 / 9 20 / 15 7 【限制】 0 <= 节点个数 <= 5000
【解题思路】
前序遍历的特点【根节点|左子树|右子树】
中序遍历的特点【左子树|根节点|右子树】
根据以上特点,可以按顺序完成以下工作:
- 确定根节点root的值,等于前序遍历的第一个元素。
- 在中序遍历中找到root值的索引位置,确定左子树和右子树的元素个数以及中序遍历。
- 根据上一步确定的元素个数,在前序遍历中确定左子树和右子树的前序遍历。
- 通过同样的方法对左(右)子树进行划分,每轮可确认三个节点的关系 。此递推性质让我们联想到用 递归方法 处理。
线性遍历了每一个节点,时间复杂度为O(n),开辟了新的hash表来存储中序遍历,因此空间复杂度为O(n)。
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: //使用一个全局的hash表来存放中序遍历中的{元素,元素下标} unordered_map<int,int> bp; TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) { //当前序或者中序数组为空,或者他们的长度不一致时,返回空 if(preorder.empty()||inorder.empty()||preorder.size()!=inorder.size()) return NULL; int n=preorder.size()-1; for(int i=0;i<=n;i++){ //依次存入hash表中 bp.insert({inorder[i],i}); } return construct(preorder,0,n,inorder,0,n); } /** * @param pre 前序遍历 * @param pb 前序遍历的开始位置 * @param pe 前序遍历的结束位置 * @param ino 中序遍历 * @param ib 中序遍历的开始位置 * @param ie 中序遍历的结束位置 */ TreeNode* construct(vector<int> pre, int pb, int pe, vector<int> ino, int ib, int ie){ if(pb>pe) //如果开始位置大于结束位置,说明没有需要处理的元素了 return NULL; int key=pre[pb]; if(bp.find(key)==bp.end()) return NULL; int index=bp[key]; //确定根节点在中序遍历中的位置,返回其下标 TreeNode* node = new TreeNode(key); //则左子树的元素个数为index-ib,递归如下 node->left=construct(pre,pb+1,pb+index-ib,ino,ib,index-1); node->right=construct(pre,pb+index-ib+1,pe,ino,index+1,ie); return node; } };
此外,也可以不使用hash表,但需要每次遍历一遍新的中序数组来确定根节点的索引,如下
TreeNode* construct(vector<int> pre, int pb, int pe, vector<int> ino, int ib, int ie){ if(pb>pe) return NULL; int key=pre[pb]; int index=ib; //仅仅此处和上面不同,通过遍历的方式来找到index,效率不如hash表高,只节省了空间 while(index<=ie && ino[index]!=key) index++; TreeNode* node = new TreeNode(key); node->left=construct(pre,pb+1,pb+index-ib,ino,ib,index-1); node->right=construct(pre,pb+index-ib+1,pe,ino,index+1,ie); return node; } };
【相关总结】
1. 什么是C++STL: unordered_map ?
unordered_map是C++中的一个关联容器,内部采用的是hash表结构,拥有快速检索的功能。其特性如下:
- 关联性:通过key去检索value,而不是通过绝对地址(和顺序容器不同)
- 无序性:使用hash表存储,内部无序
- Map : 每个值对应一个键值
- 键唯一性:不存在两个元素的键一样
- 动态内存管理:使用内存管理模型来动态管理所需要的内存空间
有关unordered_map的详细操作可参考https://blog.csdn.net/lizhengze1117/article/details/96728468
2. map和unordered_map的区别。
C++中map提供的是一种键值对容器,键不能有重复的,值可以重复,map使用键来存储数据,系统会根据键来自动将数据排序。map的value_type是pair<const key_type, mapped_type>
,所以map迭代器只能改变关键字映射的值(mapped_type),不能修改关键字。
unordered_map的用法和map是一样的,提供了 insert,size,count等操作,并且里面的元素也是以pair类型来存贮的。就外部使用来说却是一致的,但它俩的底层实现完全不同。
- map: map内部实现了一个红黑树(红黑树是非严格平衡二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素。因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行的操作。map中的元素是按照二叉搜索树(又名二叉查找树、二叉排序树,特点就是左子树上所有节点的键值都小于根节点的键值,右子树所有节点的键值都大于根节点的键值)存储的,使用中序遍历可将键值按照从小到大遍历出来。
- unordered_map: unordered_map内部实现了一个哈希表(也叫散列表,通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的。