剑指offer——重建二叉树(亚马逊面试题)
原题描述
输入一棵二叉树前序遍历和中序遍历的结果,请重建该二叉树。 注意: 二叉树中每个节点的值都互不相同; 输入的前序遍历和中序遍历一定合法; 样例 给定: 前序遍历是:[3, 9, 20, 15, 7] 中序遍历是:[9, 3, 15, 20, 7] 返回:[3, 9, 20, null, null, 15, 7, null, null, null, null] 返回的二叉树如下所示: 3 / \ 9 20 / \ 15 7 原题链接:https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/
参考代码(c++版)
/** * 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: map<int,int> hash; vector<int> preorder,inorder; TreeNode* buildTree(vector<int>& _preorder, vector<int>& _inorder) { preorder = _preorder,inorder= _inorder; for(int i = 0; i < inorder.size();i++) hash[inorder[i]] = i; return dfs(0,preorder.size()-1,0,inorder.size()-1); } TreeNode* dfs(int pl,int pr,int il, int ir) { if(pl>pr) return nullptr; auto root = new TreeNode(preorder[pl]); int k = hash[root->val]; auto left = dfs(pl+1,pl+1+k-il-1,il,k-1); auto right = dfs(pl+1+k-il,pr,k+1,ir); root->left = left; root->right = right; return root; } };
解析
涉及知识点
1.树:n(n >= 0)个结点构成的有限集合。
2.二叉树:每个结点最多有两个子树(左子树和右子树)的树结构
3.前序遍历:先访问根节点,再依次遍历左子树和右子树
4.中序遍历:先遍历左子树,再访问根节点,最后遍历右子树
5.后序遍历:先遍历左子树,再遍历右子树,最后访问根节点
6.由两种遍历序列确定二叉树–> 必须要有中序遍历。
前序是根左右,后序是左右根。都可以很快的确定根。但是对于左右子树的确定就存在争议。比如例子中的两个序列,都知道A是根,但是现在B是左子树还是右子树了?
分析
根据先序遍历序列的第一个结点确定根节点
根据根节点在中序遍历序列中分割出左右两个子序列
对左子树和右子树分别递归使用相同的方法继续分解
为了快速找到数据,考虑使用哈希表进行优化(哈希表可以实现近似O(1)时间复杂度的插入、删除、查找等操作)
代码逐步落实
1.因为之后要在递归函数中使用到前序序列和中序序列。将两个序列处理为全局变量
vector<int> preorder,inorder; //return dfs(0,preorder.size()-1,0,inorder.size()-1);
2.记录中序序列中每个数据的位置,用于后文的查询
for(int i = 0; i< inorder.size() ; i++) hash[inorder[i]] = i;
3,编写核心的递归函数dfs(int pl,int pr,int il,int ir)
pl,pr,il,ir分别代表前序序列的左端点、右端点和中序遍历序列的左端点、右端点
1.特判——倘若传入的序列里面没有元素,返回空
if(pl > pr) return nullptr;
2.获取根节点信息
auto root = new TreeNode(preorder[pl]);
3,计算根节点的哈希值
int k = hash[root->val];
4.对左右子树分别递归使用相同方法继续分解
auto left = dfs(pl+1,pl+k-il,il,k-1); auto right = dfs(pl+k-il+1,pr,k+1,ir);
5.将最终结果返回到根节点root,维护为一棵完整的树
root->left = left, root->right = right;
难点剖析
步骤3-4 中取区间范围是比较棘手的点
理解
核心是谨记这句话"对左右子树分别递归使用相同方法继续分解"
处理左子树
结合上图,对于前序遍历而言,左子树所在的区间是pl+1 ~ (pl+1)+(k-il)-1,经过合并,就得到了上述表达式pl+1 ~ pl+k-il。对于中序遍历而言,左子树所在的区间是il ~ k-1。
处理右子树
结合上图,对于前序遍历而言,右子树所在的区间是(pl+k-il+1) ~ pr。对于中序遍历而言,右子树所在的区间是(k+1) ~ ir