题目来源
题目描述
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 {
public:
TreeNode *buildTree(vector<int> &preorder, vector<int> &inorder) {
}
};
题目解析
树的算法,关键思路如下:把题目的要求细化,搞清楚根节点应该做什么,然后剩下的事情抛给前/中/后序的遍历框架就行了
思路: 构造二叉树,第一件事一定是找根节点,然后想办法构造左右子树
- 二叉树的前序和中序遍历结果的特点如下:
- 前序遍历结果第一个就是根节点,然后再根据中序遍历结果确定左右子树的节点
相关问题:
- 为什么要找根节点?
- 因为所有的数都是从根节点开始构建的
- 如何找根节点?
- 因为:
- 前序遍历: 根节点 – 左节点 – 右节点
- 中序遍历: 左节点 – 根节点 – 右节点
- 所以:
- 第一个根节点一定出现在前序遍历的第一个位置,而对于中序遍历来说,因为我们不知道第一个根节点前面左子树的长度,所以无法得知
- 因此最简单的切入点是通过前序遍历找到第一个根节点
- 因为:
- 如何找到第一个左节点
- 这个在前序遍历中也很好看出来,第一个左节点就在第一个跟几点的右边1个单位,即:
pre_root_index + 1
- 这个在前序遍历中也很好看出来,第一个左节点就在第一个跟几点的右边1个单位,即:
- 如何找第一个右节点?
- 对于前序遍历来说,我们已经知道了根节点、左节点,那么很显然,右节点的下标 = 跟节点的下标 + 左子树的长度 + 1(在前序遍历数组中)。现在唯一不确定的是左子树的长度
- 这可以通过中序遍历获得:左子树长度 = 根节点下标 - 左子树的左边界 (在中序遍历数组中)
- 如何重建二叉树?递归
过程:
- 先序数组中最左边的值就是树的头结点值,假设为h,先用h生成头结点head,然后在中序遍历数组中找到h,假设位置是i。那么在中序数组中,i左边的数组就是头结点左子树的中序数组,假设长度为5,则左子树的先序数组就是先序数组中h往右长度也为l的数组。举个例子
- 先序数组为 [ 1 , 2 , 4 , 5 , 8 , 9 , 3 , 6 , 7 ] [1, 2, 4, 5,8,9,3,6,7] [1,2,4,5,8,9,3,6,7],中序数组为 [ 4 , 2 , 8 , 5 , 9 , 1 , 6 , 3 , 7 ] [4, 2,8,5,9,1,6,3,7] [4,2,8,5,9,1,6,3,7]
- 则二叉树的头结点值为 1 1 1,在中序数组中找到 1 1 1的位置,1左边的数组为 [ 4 , 2 , 8 , 5 , 9 ] [4, 2,8,5,9] [4,2,8,5,9],是头结点左子树的中序数组,长度为 5 5 5;先序数组中1的右边长度也为5的数组为 [ 2 , 4 , 5 , 8 , 9 ] [2, 4, 5,8,9] [2,4,5,8,9],就是左子树的先序数组
- 利用左子树的先序和中序数组,递归整个过程建立左子树,返回的头结点记为left
-
i
i
i右边的数组就是头结点右子树的中序数组,假设长度为
r
r
r。先序数组中右侧等长的部分就是右节点右子树的先序数组。
- 比如,中序数组 1 1 1右边的数组为 [ 6 , 3 , 7 ] [6,3,7] [6,3,7];先序数组中右侧等长的部分为 3 , 6 , 7 3,6,7 3,6,7,它们分别为头结点右子树的中序和先序数组
- 利用右子树的先序和后序数组,递归整个过程建立右子树,返回的头结点记为right
- 把head的左孩子和右孩子分别设为left和right,返回head,过程结束
代码如下:
class Solution {
public:
TreeNode *buildTree(vector<int> &preorder, vector<int> &inorder) {
return buildTree(preorder, 0, preorder.size() - 1, inorder, 0, inorder.size() - 1);
}
TreeNode *buildTree(vector<int> &preorder, int pLeft, int pRight, vector<int> &inorder, int iLeft, int iRight) {
if (pLeft > pRight || iLeft > iRight) return NULL;
int i = 0;
for (i = iLeft; i <= iRight; ++i) {
if (preorder[pLeft] == inorder[i]) break;
}
TreeNode *cur = new TreeNode(preorder[pLeft]);
cur->left = buildTree(preorder, pLeft + 1, pLeft + i - iLeft, inorder, iLeft, i - 1);
cur->right = buildTree(preorder, pLeft + i - iLeft + 1, pRight, inorder, i + 1, iRight);
return cur;
}
};
优化:
class Solution {
private:
unordered_map<int, int> map;
public:
TreeNode* myBuildTree(const vector<int>& pre, const vector<int>& in, int pLeft, int pRight, int iLeft, int iRight) {
if (pLeft > pRight) {
return nullptr;
}
int pRoot = pLeft; // 前序遍历中的第一个节点就是根节点
int iRoot = map[pre[pRoot]]; // 在中序遍历中定位根节点
TreeNode* root = new TreeNode(pre[pRoot]); // 先把根节点建立出来
int size = iRoot - iLeft; // 得到左子树中的节点数目
// 递归地构造左子树,并连接到根节点
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
root->left = myBuildTree(pre, in, pLeft + 1, pLeft + size, iLeft, iRoot - 1);
// 递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
root->right = myBuildTree(pre, in, pLeft + size + 1, pRight, iRoot + 1, iRight);
return root;
}
TreeNode* buildTree(vector<int>& pre, vector<int>& in) {
int n = pre.size();
// 构造哈希映射,帮助我们快速定位根节点
for (int i = 0; i < n; ++i) {
map[in[i]] = i;
}
return myBuildTree(pre, in, 0, n - 1, 0, n - 1);
}
};