题目描述
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
树的存储结构:
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
解题思路:
题意容易理解,根据给出的前序和中序遍历构造出对应的二叉树。我们只需要知道前序和中序遍历的过程即可。
//树的前序遍历
private void preorder(TreeNode root){
if(root != null){
print(root); //对于当前结点的操作,简单理解成打印root.val值
preorder(root.left);
preorder(root.right)
}
}
//树的中序遍历
private void preorder(TreeNode root){
if(root != null){
preorder(root.left);
print(root); //对于当前结点的操作,简单理解成打印root.val值
preorder(root.right)
}
}
通过以上两段代码我们可以发现:
1.前序遍历时,当根结点不为空时,先打印root.val,再递归遍历root.left,再递归遍历root.right.因此,前序遍历结果集的第一个元素即为根结点。
2.中序遍历时,当根结点不为空时,先递归遍历root.left,再打印当前结点root.val,再递归遍历右子树。因此,在中序遍历结果集中,左子树中的元素都在根结点左边,右子树中的元素都在根结点右边。
根据以上两个特点,我们可以在前序遍历结果集和中序遍历结果集中,首先找出前序遍历结果集中的第一个元素,即为当前根结点
。然后再从中序遍历结果集中找出根结点对应的下标,此时将中序遍历结果集分成两部分,根结点左侧为左子树遍历结果集,根结点右侧为右子树遍历结果集,我们可以知晓左右子树结果集的元素个数。然后再根据前序遍历
的特点,即先遍历左子树再遍历右子树,因此左子树前序遍历结果集在右子树前序遍历结果集前。结合左右子树结果集元素个数,将前序遍历结果集分成两部分,分别是左子树前序遍历结果集和右子树前序遍历结果集。
此时,我们便将问题的规模变小使得可以运用递归来解决问题。
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
//用于记录结点值在中序遍历结果集中的下标
Map<Integer,Integer> memo = new HashMap<>();
for(int i = 0; i < inorder.length; i ++){
memo.put(inorder[i],i);
}
return myBuildTree(preorder,0,preorder.length,inorder,0,memo);
}
//构建二叉树
//preorder是前序序遍历结果集
//pbegin,pend 分别是preorder的起点下标与终点下标,左闭右开
//ibegin是inorder的起始下标,用于协助分割前序遍历的结果集
//memo用于保存中序遍历结果集的元素下标
private TreeNode myBuildTree(int[] preorder,int pbegin,int pend,int ibegin,Map<Integer,Integer> memo){
//preorder区间范围[pbegin,pend),左闭右开,因此当pbegin < pend时,表示preorder中仍有元素未构建
if(pbegin < pend){
int index = memo.get(preorder[pbegin]); //前序遍历集合第一个元素再中序遍历集合中出现的位置
TreeNode root = new TreeNode(preorder[pbegin]); //创建新节点
//递归获取左子树,其中pbegin+1为左子树在前序遍历结果集中的起始节点,index - ibeing为左子树中包含元素个数
root.left = myBuildTree(preorder,pbegin + 1,pbegin + index - ibegin + 1,ibegin,memo);
//递归获取右子树,其中pbegin + 1 + index - ibegin为右子树在前序遍历结果集中的起始节点
root.right = myBuildTree(preorder,pbegin + index - ibegin + 1,pend,index + 1,memo);
return root;
}
return null;
}
}
注意:
需要理解pbegin < pend
的含义;首先,使用map获取到前序首元素在中序遍历中的位置,然后index - ibegin
即为左子树元素的个数。当其值为零时,就代表左子树为空。而此时pbegin + 1
与pbegin + 1 + index - ibegin
的值相同,在递归便会返回null。右子树为空时同理。不过这样理解起来还是会有点困难,可以简单的理解成[pbegin,pend)
左闭右开,表示还未纳入树中的结点区间范围。当pbegin >= pend
时,此时范围非法,表示所有结点已经纳入树中,因此返回Null。否则就返回当前结点,并且递归的纳入其子结点。