[二叉树]105. 从前序与中序遍历序列构造二叉树

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

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

例如,给出

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

返回如下的二叉树:

        3
      /    \
    9      20
           /     \
        15       7

树的存储结构:

public class TreeNode {
     int val;
     TreeNode left;
     TreeNode right;
     TreeNode() {}
     TreeNode(int val) { this.val = val; }
     TreeNode(int val, TreeNode left, TreeNode right) {
         this.val = val;
         this.left = left;
         this.right = right;
     }
}

解题思路:
题意容易理解,根据给出的前序和中序遍历构造出对应的二叉树。我们只需要知道前序和中序遍历的过程即可。

	//树的前序遍历
	private void preorder(TreeNode root){
		if(root != null){
			print(root);			//对于当前结点的操作,简单理解成打印root.val值
			preorder(root.left);
			preorder(root.right)
		}
	}
	//树的中序遍历
	private void preorder(TreeNode root){
		if(root != null){
			preorder(root.left);
			print(root);			//对于当前结点的操作,简单理解成打印root.val值
			preorder(root.right)
		}
	}

通过以上两段代码我们可以发现:
1.前序遍历时,当根结点不为空时,先打印root.val,再递归遍历root.left,再递归遍历root.right.因此,前序遍历结果集的第一个元素即为根结点。
2.中序遍历时,当根结点不为空时,先递归遍历root.left,再打印当前结点root.val,再递归遍历右子树。因此,在中序遍历结果集中,左子树中的元素都在根结点左边,右子树中的元素都在根结点右边。
根据以上两个特点,我们可以在前序遍历结果集和中序遍历结果集中,首先找出前序遍历结果集中的第一个元素,即为当前根结点。然后再从中序遍历结果集中找出根结点对应的下标,此时将中序遍历结果集分成两部分,根结点左侧为左子树遍历结果集,根结点右侧为右子树遍历结果集,我们可以知晓左右子树结果集的元素个数。然后再根据前序遍历的特点,即先遍历左子树再遍历右子树,因此左子树前序遍历结果集在右子树前序遍历结果集前。结合左右子树结果集元素个数,将前序遍历结果集分成两部分,分别是左子树前序遍历结果集和右子树前序遍历结果集。
此时,我们便将问题的规模变小使得可以运用递归来解决问题。

class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
    	//用于记录结点值在中序遍历结果集中的下标
    	Map<Integer,Integer> memo = new HashMap<>();
    	for(int i = 0; i < inorder.length; i ++){
    		memo.put(inorder[i],i);
    	}
    	return myBuildTree(preorder,0,preorder.length,inorder,0,memo);
    }
    //构建二叉树
    //preorder是前序序遍历结果集
    //pbegin,pend 分别是preorder的起点下标与终点下标,左闭右开
    //ibegin是inorder的起始下标,用于协助分割前序遍历的结果集
    //memo用于保存中序遍历结果集的元素下标
    private TreeNode myBuildTree(int[] preorder,int pbegin,int pend,int ibegin,Map<Integer,Integer> memo){
    	//preorder区间范围[pbegin,pend),左闭右开,因此当pbegin < pend时,表示preorder中仍有元素未构建
        if(pbegin < pend){
            int index = memo.get(preorder[pbegin]); //前序遍历集合第一个元素再中序遍历集合中出现的位置
            TreeNode root = new TreeNode(preorder[pbegin]); //创建新节点
            //递归获取左子树,其中pbegin+1为左子树在前序遍历结果集中的起始节点,index - ibeing为左子树中包含元素个数
            root.left = myBuildTree(preorder,pbegin + 1,pbegin + index - ibegin + 1,ibegin,memo);
            //递归获取右子树,其中pbegin + 1 + index - ibegin为右子树在前序遍历结果集中的起始节点
            root.right = myBuildTree(preorder,pbegin + index - ibegin + 1,pend,index + 1,memo);
            return root;
        }
        return null;
    }
    
}

注意:
需要理解pbegin < pend的含义;首先,使用map获取到前序首元素在中序遍历中的位置,然后index - ibegin即为左子树元素的个数。当其值为零时,就代表左子树为空。而此时pbegin + 1pbegin + 1 + index - ibegin的值相同,在递归便会返回null。右子树为空时同理。不过这样理解起来还是会有点困难,可以简单的理解成[pbegin,pend)左闭右开,表示还未纳入树中的结点区间范围。当pbegin >= pend时,此时范围非法,表示所有结点已经纳入树中,因此返回Null。否则就返回当前结点,并且递归的纳入其子结点。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值