【剑指offer】算法题07.重建二叉树(C++)

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

例如,给出
前序遍历 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),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的。
posted @ 2020-03-18 15:28  网上冲浪小学生  阅读(130)  评论(0编辑  收藏  举报