图文并茂详解经典算法系列-1:根据指定两种序列构造二叉树

0x01.问题

从前序与中序遍历序列构造二叉树

根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。

例如,给出:

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

返回如下的二叉树:
在这里插入图片描述

0x02.算法详细分析

我们知道,根据前序序列和中序序列是可以完全确定一棵二叉树,那么我们是怎么根据前序序列和中序序列去确定这个这棵树的呢?我们一起来探究一下。

1.第一个问题:我们要明白,前序序列和中序序列是怎么得到的?

  • 这应该是比较基础的二叉树知识了。

  • 二叉树的前序序列得到的方式:

    • 遍历根节点。
    • 递归遍历左子树。
    • 递归遍历右子树。
  • 二叉树的中序序列得到的方式:

    • 递归遍历左子树。
    • 遍历根节点。
    • 递归遍历右子树。

2.第二个问题:前序序列和中序序列中的值是怎样对应原来树中的结构的呢?

  • 一定要将这个序列和原来的树结合起来,知道序列中的值是如何分布的,才能够还原原来的那棵二叉树。

  • 根据上面得到序列的方式,我们应该不难得到,下面的这个关系:
    在这里插入图片描述

  • 也就是说,前序序列的根节点在最前面,然后是左子树,然后是右子树。

  • 中序序列中先是前序序列,然后是根节点,然后是右子树。

  • 知道序列中是怎么分布后,我们似乎还没有能够构造出完整的树的思路,因为我们似乎遇到了一个困难:

    • 假如我们第一个以前序序列的第一个节点构造了树的根节点,下一个节点如何寻找?根节点的左右子树到底在序列的哪个位置?

3.深度思考一下第三个问题:如何去序列中寻找对应的左右子树?

  • 既然根节点是前序序列的第一个节点,那么根节点的左子树如何寻找?

    • 其实很简单了,左子树就是前序序列的第二个节点,因为前序序列就是先遍历左子树,再遍历右子树,所以,左子树就很好寻找了。
  • 那么右子树如何去寻找呢?

    • 我们最原始的想法很简单,就是去找到这个位置就可以了。
      在这里插入图片描述
  • 因为这个位置是右子树的第一个节点,也就是根节点的右子树。

  • 那么问题来了,中间这段距离怎么求,也就是左子树的数目。

  • 不要想当然的认为左子树和右子树的数量是相等的,因为问题只说了是二叉树,并没有说是什么特殊的二叉树,所以并不能确定左子树的数量。

  • 这个时候,不要忘记,我们还有一个中序序列,这个中序序列和前序序列所对应的二叉树可是相同的,所以自然左子树的数目也是相同的。

  • 我们的想法是,要是知道根节点在中序序列中的位置,左子树的数量就可以确定下来了。
    在这里插入图片描述

  • 而题目中说了,保证节点的值是唯一的,所以,只要一个个找,肯定是能找到的。

  • 但是这样查找就比较浪费时间,我们可以维护一个哈希表,每次去哈希表中查找,就能把时间的复杂度降下来。

4.具体的算法思路?

  • 在上面的分析中,我们确定了基本的思路,也就是依次寻找根节点,左子树,右子树。

  • 而在树中,这个过程肯定是可以抽取出来的,也就是可以使用递归的方式将这个过程表示出来。

  • 细节: 每一次递归需要哪些参数呢?

    • 由于需要确定序列中左右子树的边界,所以对于每种序列来说,左右边界当然是需要的。
  • 细节:初始化?

    • 初始化条件是0,n-1
  • 细节:递归退出的条件?

    • 当左边界大于右边界时,递归退出。

0x03.算法–从前序与中序遍历序列构造二叉树

class Solution {

    private Map<Integer,Integer> indexMap;

    private TreeNode toBuildTree(int[] preorder,int[] inorder,int pre_left,int pre_right,int in_left,int in_right){
        //递归终止条件
        if(pre_left>pre_right){
            return null;
        }
        //建立根节点
        TreeNode root=new TreeNode(preorder[pre_left]);
        //得到中序序列中根节点的下标
        int in_root=indexMap.get(preorder[pre_left]);
        //得到前序序列中左子树的长度
        int size_left=in_root-in_left;
        //递归建立左子树
        root.left=toBuildTree(preorder,inorder,pre_left+1,pre_left+size_left,in_left,in_root-1);
        //递归建立右子树
        root.right=toBuildTree(preorder,inorder,pre_left+size_left+1,pre_right,in_root+1,in_right);
        return root;
    }

    public TreeNode buildTree(int[] preorder, int[] inorder) {
        int n=preorder.length;
        if(n!=inorder.length){
            return null;
        }
        indexMap=new HashMap<>();
        for(int i=0;i<n;i++){
            indexMap.put(inorder[i],i);
        }
        return toBuildTree(preorder,inorder,0,n-1,0,n-1);
    }
}

细节理解:关于边界参数的变换,都在这张图里可以表示出来:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ATFWUS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值