剑指Offer每日一题5、重建二叉树8月更文

一、前言

大家好,本文章属于《剑指 Offer 每日一题》中的系列文章中的第 5 篇。

在该系列文章中我将通过刷题练手的方式来回顾一下数据结构与算法基础,同时也会通过博客的形式来分享自己的刷题历程。如果你刚好也有刷算法题的打算,可以互相鼓励学习。我的算法基础薄弱,希望通过这两三个月内的时间弥补这块的漏洞。本次使用的刷题语言为 Java ,预计后期刷第二遍的时候,会采用 Python 来完成。

二、题目

输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。

假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

示例1:

img

 Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
 Output: [3,9,20,null,null,15,7]
复制代码

示例2:

 Input: preorder = [-1], inorder = [-1]
 Output: [-1]
复制代码

三、解题

3.1 思路1:递归

知识点:

  • 前序遍历列表:第一个元素永远是【根节点root】
  • 中旬遍历列表,根节点 root 的所有元素都在根节点的左分支,右边元素都在根节点的右分支。

算法思路:

  • 1、通过前序遍历列表确定【根节点】
  • 2、将【中序遍历列表】的节点分割成【左分支节点】和【右分支节点】
  • 3、递归寻找【左分支节点】中的【根节点】和右分支节点的【根节点】
3.1.1 代码
     // 每次传入前序数组和后序数组,构建出二叉树
     public static TreeNode buildTree(int[] preorder,int[] inorder){
         // 1、首先是啥时候结束,当传入数组长度为 0 的时候就该结束了
         if(preorder.length==0){
             return null;
         }
         /**
          *  2、接下来我们应该返回什么?那当然是返回重建子树的根节点啦
          *    2.1 先获取根节点
          *    2.2 因为没有重复值,在中序遍历中找到左右子树
          */
         int rootValue=preorder[0];
         int rootIndex =0;
         for (int i = 0; i < inorder.length; i++) {
             if(inorder[i]==rootValue){
                 rootIndex=i;
             }
         }
         /**
          * 3、我们找到了根和左右子树,我们可以用同样的方法去构建左子树和右子树,用递归实现
          */
         TreeNode root = new TreeNode(rootValue);
         root.left=buildTree(Arrays.copyOfRange(preorder,1,rootIndex+1),Arrays.copyOfRange(inorder,0,rootIndex));
         root.right=buildTree(Arrays.copyOfRange(preorder,rootIndex+1,preorder.length),Arrays.copyOfRange(inorder,rootIndex+1,inorder.length));
         return root;
 }
复制代码
3.1.2 执行效果

image-20210802214356183

3.2 思路2:递归2

上面的方法因为出现频繁的拷贝数组,所以效率特别的低,因此,这种方法里可以对数组做个改变,从map 中获取中间数,具体思路如下:

  • 1、建立根节点 node :节点值为 preorder[root]
  • 2、划分左右子树:查找根节点在中序遍历 inorder 中的索引i(使用map 的键值对查找)
  • 3、构建左子树
3.2.1 代码
 class Solution {
  HashMap<Integer, Integer> map = new HashMap<>();//标记中序遍历
     int[] preorder;//保留的先序遍历,方便递归时依据索引查看先序遍历的值
 ​
     public TreeNode buildTree(int[] preorder, int[] inorder) {
         this.preorder = preorder;
         //将中序遍历的值及索引放在map中,方便递归时获取左子树与右子树的数量及其根的索引
         for (int i = 0; i < inorder.length; i++) {
             map.put(inorder[i], i);
         }
         //三个索引分别为
         //当前根的的索引
         //递归树的左边界,即数组左边界
         //递归树的右边界,即数组右边界
         return recur(0,0,inorder.length-1);
     }
 ​
     TreeNode recur(int pre_root, int in_left, int in_right){
         if(in_left > in_right) return null;// 相等的话就是自己
         TreeNode root = new TreeNode(preorder[pre_root]);//获取root节点
         int idx = map.get(preorder[pre_root]);//获取在中序遍历中根节点所在索引,以方便获取左子树的数量
         //左子树的根的索引为先序中的根节点+1 
         //递归左子树的左边界为原来的中序in_left
         //递归右子树的右边界为中序中的根节点索引-1
         root.left = recur(pre_root+1, in_left, idx-1);
         //右子树的根的索引为先序中的 当前根位置 + 左子树的数量 + 1
         //递归右子树的左边界为中序中当前根节点+1
         //递归右子树的有边界为中序中原来右子树的边界
         root.right = recur(pre_root + (idx - in_left) + 1, idx+1, in_right);
         return root;
 ​
     }
 }
复制代码
3.2.2 执行效果

image-20210802213220104

3.2.3 复杂度分析

遍历的是 HashMap,占用O(n),搜索操作占用O(1),所以时间复杂度为 O(1).

空间复杂度:最好情况下为满二叉树,占用O(logN)的额外空间,最差情况下占用O(N)

四、总结

本题是遇到的第一个树题,考察了对二叉树的前序遍历和中序遍历的理解程度,同时将以前选择题中的前序中序找后序序列的题目转换成了代码,本题难度属于中等,面对这类复杂问题,需要具备化解问题的能力,本题中的两种解法都是采用递归来完成的。

今天的刷题就到这了,欢迎点赞评论交流,剑指 Offer 刷题之旅将继续展开!