其实是算法题,各参考资料有对应题解,如LeetCode-105,本文只是官方题解以及不同解法的解析比较,思想最重要,理解为最终目的,若有错误,恳请指正,详情请参考原版,谢谢!!
1.力扣官方题解
官方题解的结构是有一个工具方法处理所传入的索引区间内的元素(找根并划分左右),有点类似二分法查找的思想,递归调用使得区间越来越小,直至为1;外层方法作为递归调用的一个初始入口,调用后返回工具方法的执行结果。
class Solution{
//从第一个前序序列元素开始
int pre_idx = 0;
int[] preorder;
int[] inorder;
HashMap<Integer,Integer> idx_map = new HashMap<Integer,Integer>();
//该类为工具类,作用是对传入的索引区间内辖元素进行左右子树的划分处理,由buildTree调用
public TreeNode buildTool(int in_left, int in_right){//参数为传入的列表第一和最后一个元素的索引
//若为空
if(in_left == in_right){
return null;
}
//选定preorder的第一个元素为root
int root_val = preorder[pre_idx];
TreeNode root = new TreeNode(root_val);
//root对应的元素在inorder中将序列一分为二:左子树序列和右子树序列
//从hashmap中获取到root_val对应的索引,在中序序列中索引左即左子树序列,右即右子树序列部分
int index = idx_map.get(root_val);
//递归调用
pre_idx++;
//构建左子树
root.left = buildTool(in_left,index);
//构建右子树
root.right = buildTool(index+1,in_right);
return root;
}
//外层方法类,其调用TreeTool,然后TreeTool递归调用自身
public TreeNode buildTree(int[] preorder,int[]inorder){//入参是前序序列和中序序列
this.preorder = preorder;
this.inorder = inorder;
//以中序序列构建一个hashmap(VALUE->INDEX)
int idx = 0;
for(Integer val : inorder){
idx_map.put(val,idx++);
}
return buildTool(0,inorder.length);//调用TreeTool,入参是中序序列的最大最小索引值,然后TreeTool递归调用自身,入参的区间大小逐步减小直至为1,即只有一个元素
}
}
2.题解
题解的结构与官方题解类似,但是更为精炼,其中递归构建左右子树时候的第3个参数很关键,构建左子树时,其根节点是preIndex对应的元素,则由前序遍历的性质,preIndex+1对应的元素即preIndex对应元素的左子,即左子树构成的序列范围内的‘根节点’;构建右子树时,其根节点通过计算可得:上一次处理得到根节点在中序序列的索引值为i,即前序序列除preIndex后紧接着(i-inFrom+1)个元素都是左子树的元素,则右子树构成的序列范围内的‘根节点’的索引值为preIndex+i-inFrom+1,这里为什么是i-inFrom+1,是因为在中序序列中寻找根节点对应在中序序列中的索引值时是从inFrom开始的,也即本次确定的左节点元素个数是i-inFrom+1,这一点比较绕。
class Solution {
//主方法,初始调用buildCore
public TreeNode buildTree(int[] preorder, int[] inorder) {//入参为前序序列和中序序列
return buildCore(preorder,inorder,0,0,inorder.length);//初始调用buildCore
}
//静态方法,处理传入的前序序列preList、中序序列inList,
//本次的根元素在前序序列中的索引preIndex、本次所要处理的中序序列的索引区间上下限inFrom,inTo
public static TreeNode buildCore(int[] preList,int[] inList,int preIndex,int inFrom,int inTo){
//处理为空的情况
if(inFrom >= inTo){//说明传进来的待构建的子树序列为空,可能为左子树或右子树
return null;
}
//以前序序列中可确定的preIndex对应的元素为这一层的根构建树结构
TreeNode root = new TreeNode(preList[preIndex]);
for(int i=inFrom;i<inTo;i++){//中序序列寻找当前节点
if(inList[i] == preList[preIndex]){
//开始递归调用构建子树
root.left = buildCore(preList,inList,preIndex + 1,inFrom, i);//对左子树进行递归
root.right = buildCore(preList,inList,preIndex+i-inFrom+1,i+1,inTo);//对右子树进行递归
break;//跳出循环
}
}
return root;
}
}
3.题解
题解分析
入手是从前序序列的第一个元素入手,该元素就是树的根,拿着这个根元素的值,在中序序列中寻找对应索引,则找到索引左侧为左子树元素,右侧为右子树元素,两边的长度都好确定,这里有用的是左子树长度,由左子树长度可得在前序序列中左子树的结束索引和右子树的起始索引,这样信息已足够(确定了前序遍历的上下索引,中序遍历的上下索引),可进行递归调用了,即下边方法中的buildNode入参
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
//处理特殊情况,参数为空,这一部分是全局的处理,位置可以挪至buildCore内最开始处,见/**/注释代码
if(preorder == null || inorder == null || preorder.length != inorder.length){
return null;
}
return buildNode(preorder,0,preorder.length-1,inorder,0,inorder.length-1);
}
private TreeNode buildNode(int[] preorder,int preStart,int preEnd,int[] inorder, int inStart, int inEnd){
/*
//特殊情况处理
if(preorder == null || inorder == null || preorder.length == 0){
return null;
}
*/
//因前序遍历数组的第一个元素为根,取前序遍历的第一个元素开始构建树
int rootValue = preorder[preStart];
TreeNode root = new TreeNode(rootValue);
//边界情况处理:前序序列只有根节点,即只有一个元素
if(preStart == preEnd){
return root;
}
//遍历中序序列,找根节点,从inStart开始
int rootInorder = inStart;
while(inorder[rootInorder]!=rootValue && rootInorder<=inEnd){
rootInorder++;
}
/*构建左右子树*/
//获取左子树的长度,得到左子树最后一个元素的索引
int leftLength = rootInorder - inStart;
//前序序列中左子树的最后一个元素索引(为迭代进buildNode的第三个入参)
//从中序序列可得根元素左子树的元素个数leftLength,则由前序序列的性质(Node->Left->Right)
//前序序列根元素Node(第一个元素)后leftLength个元素定全是根的左子树元素,计算左子树的结束索引
int leftPreEnd = preStart + leftLength;
//若左子树非空,则递归构建
if(leftLength>0){
root.left = buildNode(preorder,preStart+1,leftPreEnd,inorder,inStart,rootInorder-1);
}
//若右子树非空,则递归构建
if(leftLength<preEnd-preStart){
root.right = buildNode(preorder,leftPreEnd+1,preEnd,inorder,rootInorder+1,inEnd);
}
return root;
}
}