思路:要想完成这道编程题,首先你得很清楚,如何通过一个前序遍历序列和一个中序遍历序列,重建一棵二叉树?如果你不清楚这个过程,没关系,我尽自己最大能力去讲解。如果还是不懂,可以考虑上网搜一些图文并茂的资料,可能会好很多。

  1. 可以这样认为,前序遍历序列中的每一个节点,都是根节点(叶子节点可以看作根节点,只不过是左右孩子都为 null 的根节点罢了)。
  1. 然后,我们每一次从前序遍历序列中取出一个"根节点",在中序遍历序列中找到它所在的位置。
  1. 以它为中心,可以将整个中序遍历序列分成两部分(如果此时这个点在中序遍历序列的边上,可以认为它其中的一部分为空),左半边是它的左子树,右半边是它的右子树。
  1. 接着,分别对它的左半边和右半边重复上述的操作(再从前序遍历序列中获取一个“根节点”,对它的左半边进行切分),最终我们就可以重建出一棵二叉树。

注:本人表达能力有限,实在看不懂的,可以自己动手画一画,或者上网搜更好的资料学习。

系统给定的函数

         
        // 一些特殊情况的处理
        if (0 == pre.length) { // 如果 pre 数组的长度为 0,直接返回一个 null 即可
            return null;
        }
        if (1 == pre.length) { // 如果 pre 数组的长度为 1,直接返回以 pre 数组中唯一的一个数为 val 值的 TreeNode 即可
            return new TreeNode(pre[0]);
        }
         
        // 思路:要想完成这道编程题,首先你得很清楚,如何通过一个前序遍历序列和一个中序遍历序列,重建一棵二叉树?如果你不清楚这个过程,没关系,我尽自己最大能力去讲解。如果还是不懂,可以考虑上网搜一些图文并茂的资料,可能会好很多。
        // 可以这样认为,前序遍历序列中的每一个节点,都是根节点(叶子节点可以看作根节点,只不过是左右孩子都为 null 的根节点罢了)。
        // 然后,我们每一次从前序遍历序列中取出一个"根节点",在中序遍历序列中找到它所在的位置。
        // 以它为中心,可以将整个中序遍历序列分成两部分(如果此时这个点在中序遍历序列的边上,可以认为它其中的一部分为空),左半边是它的左子树,右半边是它的右子树。
        // 接着,分别对它的左半边和右半边重复上述的操作(再从前序遍历序列中获取一个“根节点”,对它的左半边进行切分),最终我们就可以重建出一棵二叉树。
        // 注:本人表达能力有限,实在看不懂的,可以自己动手画一画,或者上网搜更好的资料学习。
         
        // 好啦,当你已经清楚如何通过一个前序遍历序列和一个中序遍历序列,重建一棵二叉树后,我们就可以考虑如何编写代码了。
         
        // 1)由于我们每一次都要从前序遍历序列中获取一个"根节点",因此我们将题目传进来的 int 数组转换成 LinkedList 进行存储。这样,我们就可以保证每一次的递归造树,队列的队头都是当前树的根节点
        LinkedList<Integer> linkedList = new LinkedList<>();
        // 2)ArrayList 数组存放的是中序遍历序列。(没啥原因,单纯就是用得习惯,哈哈)
        ArrayList<Integer> arrayList = new ArrayList<>();
        // 3)HashMap 存放的是,在中序遍历序列中,每个节点和它的下标一一对应的关系。注意,这个 HashMap 很重要,关系到你从前序遍历序列中获取到一个"根节点"后,如何在中序遍历序列中找到它的位置!!!
        HashMap<Integer, Integer> hashMap = new HashMap<>();
         
        // 以下这几段代码就没什么好说的啦,无非就是将题目给的两个 int 数组,分别转换成 LinkedList 和 ArrayList,同时构造出 HashMap,然后调用递归函数,最后再返回根节点。
        for (int tmp : pre) {
            linkedList.add(tmp);
        }
        for (int i = 0; i < vin.length; i++) {
            arrayList.add(vin[i]);
            hashMap.put(vin[i], i);
        }
        TreeNode root = process(linkedList, arrayList, hashMap, 0, arrayList.size() - 1);
        return root;
         
    }

接下来的递归函数,才是整个算法的核心!!!重点讲解!!!

  1. 首先,我们应该考虑这个递归函数是要做什么的,先不要考虑递归的终止条件。
  2. 当你以下代码都搞懂之后,我们就可以考虑如何终止递归了。
  3. 前面我并没有讲清楚,所谓的分割中序遍历序列,并不是真的分割了 ArrayList 这个数组。准确来说,这数组无论哪一次递归,都是同一个。
  4. 所谓的切割,我们是通过 start 和 end 这两个值来定义的,它限制了当前递归中 ArrayList 数组的范围。
  5. 也就是说,ArrayList 永远是你的 ArrayList,它不会变,变的只是 start 和 end,它限制了你当前递归中 ArrayList 的检索范围!!!
  6. 既然如此,递归的终止条件就可以通过 start 和 end 来实现了。(具体为什么满足这个,就是终止,要自己调试,才能明白。主要是我说不清楚,哈哈)

递归函数的实现如下(代码里还有很详细的注释哦)

     * 参数说明
     *     pre           前序遍历序列
     *     vin           中序遍历序列
     *     nodeIndexs    就是上面我们构造的 HashMap
     *     start、end    共同表示的是,当前中序遍历序列的范围
     */
    // 以下详细讲解以下这个递归函数的思路(记得每一个参数的含义以及它的变量名)
    public TreeNode process(LinkedList<Integer> pre, ArrayList<Integer> vin, HashMap<Integer, Integer> nodeIndexs, int start, int end) {
         
        // 首先,我们应该考虑这个递归函数是要做什么的,先不要考虑递归的终止条件。
        // 当你以下代码都搞懂之后,我们就可以考虑如何终止递归了。
        // 前面我并没有讲清楚,所谓的分割中序遍历序列,并不是真的分割了 ArrayList 这个数组。准确来说,这数组无论哪一次递归,都是同一个。
        // 所谓的切割,我们是通过 start 和 end 这两个值来定义的,它限制了当前递归中 ArrayList 数组的范围。
        // 也就是说,ArrayList 永远是你的 ArrayList,它不会变,变的只是 start 和 end,它限制了你当前递归中 ArrayList 的检索范围!!!
        // 既然如此,递归的终止条件就可以通过 start 和 end 来实现了。(具体为什么满足这个,就是终止,要自己调试,才能明白。主要是我说不清楚,哈哈)
        if (start > end || pre.isEmpty()) {
            return null;
        }
        if (start == end) {
            return new TreeNode(pre.poll());
        }
         
        // 然后,pre 出队一个数值,以这个数值构造一个 TreeNode 节点,同时,通过 nodeIndexs,找到这个节点在中序遍历序列中的下标。
        // 接下来我们要做的,其实就是构造一棵以这个节点为根节点的一棵二叉树。
        int val = pre.poll();
        TreeNode root = new TreeNode(val);
        int index = nodeIndexs.get(val);
        // 接着,以这个节点为中心,将中序遍历序列切割成两部分,将这两部分分别丢给递归函数,让递归函数帮你把左子树和右子树构造好,并返回左右孩子节点
        TreeNode leftNode = process(pre, vin, nodeIndexs, start, index - 1);
        TreeNode rightNode = process(pre, vin, nodeIndexs, index + 1, end);
        // 最后,我们再将根节点与左右孩子关联起来,并返回。
        root.left = leftNode;
        root.right = rightNode;
        return root;
        // 关于递归,很抱歉,其实我也不是很清楚,一般都是凭经验、凭感觉写的。像上面两个递归(构造左右子树),你只需要知道,将所有参数丢进去,系统就会帮你构造出左右子树了,并把左右孩子节点返回给你。具体的底层原理,不要理会(我是真的不是很懂,挠头)
    }

完整的代码实现如下

/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] vin) {
        
        // 一些特殊情况的处理
        if (0 == pre.length) { // 如果 pre 数组的长度为 0,直接返回一个 null 即可
            return null;
        }
        if (1 == pre.length) { // 如果 pre 数组的长度为 1,直接返回以 pre 数组中唯一的一个数为 val 值的 TreeNode 即可
            return new TreeNode(pre[0]);
        }
        
        // 思路:要想完成这道编程题,首先你得很清楚,如何通过一个前序遍历序列和一个中序遍历序列,重建一棵二叉树?如果你不清楚这个过程,没关系,我尽自己最大能力去讲解。如果还是不懂,可以考虑上网搜一些图文并茂的资料,可能会好很多。
        // 可以这样认为,前序遍历序列中的每一个节点,都是根节点(叶子节点可以看作根节点,只不过是左右孩子都为 null 的根节点罢了)。
        // 然后,我们每一次从前序遍历序列中取出一个"根节点",在中序遍历序列中找到它所在的位置。
        // 以它为中心,可以将整个中序遍历序列分成两部分(如果此时这个点在中序遍历序列的边上,可以认为它其中的一部分为空),左半边是它的左子树,右半边是它的右子树。
        // 接着,分别对它的左半边和右半边重复上述的操作(再从前序遍历序列中获取一个“根节点”,对它的左半边进行切分),最终我们就可以重建出一棵二叉树。
        // 注:本人表达能力有限,实在看不懂的,可以自己动手画一画,或者上网搜更好的资料学习。
        
        // 好啦,当你已经清楚如何通过一个前序遍历序列和一个中序遍历序列,重建一棵二叉树后,我们就可以考虑如何编写代码了。
        
        // 1)由于我们每一次都要从前序遍历序列中获取一个"根节点",因此我们将题目传进来的 int 数组转换成 LinkedList 进行存储。这样,我们就可以保证每一次的递归造树,队列的队头都是当前树的根节点
        LinkedList<Integer> linkedList = new LinkedList<>();
        // 2)ArrayList 数组存放的是中序遍历序列。(没啥原因,单纯就是用得习惯,哈哈)
        ArrayList<Integer> arrayList = new ArrayList<>();
        // 3)HashMap 存放的是,在中序遍历序列中,每个节点和它的下标一一对应的关系。注意,这个 HashMap 很重要,关系到你从前序遍历序列中获取到一个"根节点"后,如何在中序遍历序列中找到它的位置!!!
        HashMap<Integer, Integer> hashMap = new HashMap<>();
        
        // 以下这几段代码就没什么好说的啦,无非就是将题目给的两个 int 数组,分别转换成 LinkedList 和 ArrayList,同时构造出 HashMap,然后调用递归函数,最后再返回根节点。
        for (int tmp : pre) {
            linkedList.add(tmp);
        }
        for (int i = 0; i < vin.length; i++) {
            arrayList.add(vin[i]);
            hashMap.put(vin[i], i);
        }
        TreeNode root = process(linkedList, arrayList, hashMap, 0, arrayList.size() - 1);
        return root;
        
    }
    
    /*
     * 参数说明
     *     pre           前序遍历序列
     *     vin           中序遍历序列
     *     nodeIndexs    就是上面我们构造的 HashMap
     *     start、end    共同表示的是,当前中序遍历序列的范围
     */
    // 以下详细讲解以下这个递归函数的思路(记得每一个参数的含义以及它的变量名)
    public TreeNode process(LinkedList<Integer> pre, ArrayList<Integer> vin, HashMap<Integer, Integer> nodeIndexs, int start, int end) {
        
        // 首先,我们应该考虑这个递归函数是要做什么的,先不要考虑递归的终止条件。
        // 当你以下代码都搞懂之后,我们就可以考虑如何终止递归了。
        // 前面我并没有讲清楚,所谓的分割中序遍历序列,并不是真的分割了 ArrayList 这个数组。准确来说,这数组无论哪一次递归,都是同一个。
        // 所谓的切割,我们是通过 start 和 end 这两个值来定义的,它限制了当前递归中 ArrayList 数组的范围。
        // 也就是说,ArrayList 永远是你的 ArrayList,它不会变,变的只是 start 和 end,它限制了你当前递归中 ArrayList 的检索范围!!!
        // 既然如此,递归的终止条件就可以通过 start 和 end 来实现了。(具体为什么满足这个,就是终止,要自己调试,才能明白。主要是我说不清楚,哈哈)
        if (start > end || pre.isEmpty()) {
            return null;
        }
        if (start == end) {
            return new TreeNode(pre.poll());
        }
        
        // 然后,pre 出队一个数值,以这个数值构造一个 TreeNode 节点,同时,通过 nodeIndexs,找到这个节点在中序遍历序列中的下标。
        // 接下来我们要做的,其实就是构造一棵以这个节点为根节点的一棵二叉树。
        int val = pre.poll();
        TreeNode root = new TreeNode(val);
        int index = nodeIndexs.get(val);
        // 接着,以这个节点为中心,将中序遍历序列切割成两部分,将这两部分分别丢给递归函数,让递归函数帮你把左子树和右子树构造好,并返回左右孩子节点
        TreeNode leftNode = process(pre, vin, nodeIndexs, start, index - 1);
        TreeNode rightNode = process(pre, vin, nodeIndexs, index + 1, end);
        // 最后,我们再将根节点与左右孩子关联起来,并返回。
        root.left = leftNode;
        root.right = rightNode;
        return root;
        // 关于递归,很抱歉,其实我也不是很清楚,一般都是凭经验、凭感觉写的。像上面两个递归(构造左右子树),你只需要知道,将所有参数丢进去,系统就会帮你构造出左右子树了,并把左右孩子节点返回给你。具体的底层原理,不要理会(我是真的不是很懂,挠头)
    }
}