105. 从前序与中序遍历序列构造二叉树(java实现)--LeetCode

题目:

根据一棵树的前序遍历与中序遍历构造二叉树。

注意:
你可以假设树中没有重复的元素。

例如,给出

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]

返回如下的二叉树:

    3
   / \
  9  20
    /  \
   15   7

解法1:递归

光头哥原文:https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/discuss/34543/Simple-O(n)-without-map

/**
 * preorder: [1, 2, 4, 5, 3, 6]
 * inorder: [4, 2, 5, 1, 6, 3]
 * 1.preorder的第一个元素1是根
 * 2.搜索中序
 * 3.通过中序分割成两部分[4,2,5]和[6,3]
 * 4.将前序遍历除根节点外的其余部分分为两部分,和中序遍历一样大。[2,4,5]和[3,6]
 * 5.使用preorder = [2, 4, 5] and inorder = [4, 2, 5] 添加左子树
 * 6.使用preorder =[3, 6] and inorder = [6, 3] 添加右子树
 *
 * 第一步:考虑最坏的情况,树退化成了链表,集中在左子树上。此时前序遍历的结果反转过来是中序的结果1234,4321
 * 第二步:在中序中查找,根据您如何“拆分”数组,您将可能看到,时间复杂度O(n^2) ,空间复杂度O(n^2)
 * 在开始主要工作之前,通过构建一个map,k是值,v是在中序中的索引位置,可以将搜索的运行时降到O(n),我已经看到几种解决方案可以做到这一点。
 * 但这是O(n)额外的空间,分裂问题仍然存在。要解决这些问题,可以在前序和中序中使用指针,而不是拆分它们。当你这么做的时候,你也不需要map索引的kv。
 *
 * 再考虑一下这个例子。不要在中序中找根,而是将数组分成几个部分并在它们上递归,只需在剩余的完整数组上递归,并在中序中遇到根时停止。这就是我上面的解决方案。
 * 它将自己的根值root.val作为stopper赋给它的左子树调用,将其父级的stopper作为stopper赋给它的右子树调用。
 */
    private int in = 0;
    private int pre = 0;
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return build(preorder, inorder, Integer.MIN_VALUE);
    }

    private TreeNode build(int[] preorder, int[] inorder, int stop) {
        if (pre >= preorder.length) return null;
        if (inorder[in] == stop) {
            in++;
            return null;
        }
        TreeNode node = new TreeNode(preorder[pre++]);
        node.left = build(preorder, inorder, node.val);
        node.right = build(preorder, inorder, stop);
        return node;
    }

时间复杂度:On

空间复杂度:O1
在这里插入图片描述

解法2:递归(光头哥说的On^2的情况)

/**
 * 思路:
 *      1
 *    2   3
 *  4  5 6  7
 *
 * 前序:1245 367
 * 中序:425 1 637
 *
 * 根据中序遍历我们可以知道根节点的位置,以及根节点的左子树和右子树各有多少个节点:425 1 637
 * 在知道左子树和右子树个数的情况下,我们就能在前序遍历中找到左子树和右子树的起点和终点
 * 之后递归的设置左右子树
 *
 * 左子树
 * 前序:根+1 到 根+左子树个数+1。--1245
 * 中序:i_start 到 根的位置。--425 1
 *
 * 右子树
 * 前序:根+移动的位置+1 到 结束的位置。--367
 * 中序:根+1 到 结束的位置--637
 */
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return recursive(preorder,0, preorder.length,inorder,0,inorder.length);
    }

    private TreeNode recursive(int[] preorder, int p_start, int p_end, int[] inorder, int in_start, int in_end) {
        if (p_start==p_end)return null;
        TreeNode root = new TreeNode(preorder[p_start]);
        int in_root_index=0;
        for (int i=in_start;i<in_end;i++){
            if (inorder[i]==preorder[p_start]){
                in_root_index=i;
                break;
            }
        }
        int leftNum=in_root_index-in_start;
        root.left=recursive(preorder,p_start+1,p_start+leftNum+1,inorder,in_start,in_root_index);
        root.right=recursive(preorder,p_start+leftNum+1,p_end,inorder,in_root_index+1,in_end);
        return root;
    }

时间复杂度:On^2

空间复杂度:O1
在这里插入图片描述

解法3:递归(光头哥说的用map降到On的情况)

    /**
     * 思路:
     * 本题有一个前提条件,假设树中没有重复元素,所以可以采用map来存储中序遍历的值
     * 前序的p_start就是根,从中序中就可以划分左右子树
     * 记录中序中移动的位置,之后就可以划分了
     */
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i=0;i<inorder.length;i++){
            map.put(inorder[i],i);
        }
        return recursive(preorder,0,preorder.length,inorder,0,inorder.length,map);
    }

    private TreeNode recursive(int[] preorder, int p_start, int p_end, int[] inorder, int i_start, int i_end, HashMap<Integer, Integer> map) {
        if (p_start==p_end)return null;
        int root_val = preorder[p_start];
        TreeNode root = new TreeNode(root_val);
        int i_root_index = map.get(root_val);
        int leftNum = i_root_index - i_start;
        root.left=recursive(preorder,p_start+1,p_start+leftNum+1,inorder,i_start,i_root_index,map);
        root.right=recursive(preorder,p_start+leftNum+1,p_end,inorder,i_root_index+1,i_end,map);
        return root;
    }

时间复杂度:On

空间复杂度:On
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值