[杨小白]_java_leetcode 剑指 Offer 07. 重建二叉树

71 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第8天,点击查看活动详情

前言

小白算法比较菜,希望能激励我每日更新,从leetcode第一题开始,2022年目标300题,记录从0到1的全过程!!

剑指 Offer 07. 重建二叉树

剑指 Offer 07. 重建二叉树

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

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

示例1

image.png

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]

2.解析

前两天写了斐波那契数、爬楼梯、楼梯最小花费和机器人路径数,都分享了两个做法,递归和记忆数组。今天继续复习这一知识点。想看前三题的点下面链接。

# [杨小白]_java_leetcode 509.斐波那契数

# [杨小白]_java_leetcode 70.爬楼梯

# [杨小白]_java_leetcode 746. 使用最小花费爬楼梯

# [杨小白]_java_leetcode 62. 不同路径

2.1解法(递归法)

前序遍历的root节点,在中序遍历的中间,中序遍历的root节点之前,是left子树,root节点之后是right子树,运用这一特征就很好的解决了root节点的问题,对于root节点来说,root.val = preorder[0],root.left 就是对中序遍历root节点之前的进行重建,root.right 就是对中序遍历root节点之后的进行重建。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if(preorder.length==0) return null;
        TreeNode temp = new TreeNode(preorder[0]);
        for(int i = 0; i < preorder.length; i++) {
            if(preorder[0]==inorder[i]) {
                temp.left = buildTree(Arrays.copyOfRange(preorder,1,i+1),Arrays.copyOfRange(inorder,0,i));
                temp.right = buildTree(Arrays.copyOfRange(preorder,i+1,preorder.length),Arrays.copyOfRange(inorder,i+1,inorder.length));
                break;
            }
        }
        return temp;
    }
}

提交排名

image.png

代码简洁明了,排名几乎垫底。

2.2解法(记忆数组)

解法一的问题在于,需要cope数组,浪费了大量时间,那么我们能不能直接把原数组的l和r作为双指针传入递归函数呢?并且用一个map储存了前序遍历中的值,在中序遍历中所对应的位置。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    private Map<Integer, Integer> inOrder_Index_Map;
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if(preorder.length==0) return null;
        inOrder_Index_Map = new HashMap<>();
        for(int i = 0; i < inorder.length; i++) {
            inOrder_Index_Map.put(inorder[i],i);
        }

        return build(preorder,0,preorder.length-1,inorder,0,inorder.length-1);
    }
    public TreeNode build(int[] preorder, int start_preorder,int end_preorder, int[] inorder,int start_inorder, int end_inorder){
        if(start_preorder>end_preorder||start_inorder>end_inorder){
            return null;
        }
        TreeNode temp = new TreeNode(preorder[start_preorder]);
        int pindex = inOrder_Index_Map.get(preorder[start_preorder]);
        int sp = start_preorder + 1;
        int ep = start_preorder + pindex - start_inorder;
        int si = start_inorder;
        int ei = pindex - 1;
        temp.left = build(preorder,sp,ep,inorder,si,ei);
        
        sp = start_preorder + 1 + pindex -start_inorder;
        ep = end_preorder;
        si = pindex + 1;
        ei = end_inorder;
        temp.right = build(preorder,sp,ep,inorder,si,ei);
        return temp;
    }
}

提交排名

image.png

时间复杂度大大减小,内存消耗也减少了一半。

其中难以理解的是sp、ep、si、ei的求解,sp是start_preorder前序遍历起点的缩写,ep是end_preorder前序遍历终点的缩写,si是start_inorder中序遍历起点的缩写,ei是end_inorder中序遍历终点的缩写。

3.结束

利用原有的数据,只用较少的指针性的值去记录不同的情况,避免需要反复创建新的对象,以此来减少递归的复杂度。

递归因为有其优良的简便性,随之而来的就是时间,空间的大量占用,所以每一个简单递归算法的背后,都有大量可以优化的点,大家可以仔细钻研总结。