LC 106.从中序与后序遍历序列构造二叉树

106. 从中序与后序遍历序列构造二叉树

给定两个整数数组 inorderpostorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树

示例 1:

输入: inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]

示例 2:

输入: inorder = [-1], postorder = [-1]
输出:[-1]

提示:

  • 1 ≤ i n o r d e r . l e n g t h ≤ 3000 1 \leq inorder.length \leq 3000 1inorder.length3000
  • postorder.length == inorder.length
  • − 3000 ≤ i n o r d e r [ i ] , p o s t o r d e r [ i ] ≤ 3000 -3000 \leq inorder[i], postorder[i] \leq 3000 3000inorder[i],postorder[i]3000
  • inorderpostorder 都由 不同 的值组成
  • postorder 中每一个值都在 inorder
  • inorder 保证是树的中序遍历
  • postorder 保证是树的后序遍历

解法一(递归+分治+Map哈希)

思路分析:

  1. 对于该题,首先思考;中序遍历为:左中右,后序遍历为:左右中,因此通过后序遍历可以确认二叉树的根节点,然后通过根节点可以对中序遍历进行切割成:左中序右中序;然后根据得到的左中序长度,可以对后序遍历进行切割成:左后序右后序
  2. 以此类推,通过递归分治的方式,可以从根节点建立一个二叉树。
  3. 同时思考递归的参数和返回值,因为题目要求构造一个二叉树,所以 返回值类型为TreeNode,然后对于递归的参数则包括,中序遍历数组、后序遍历数组、中序数组起始位置、中序数组末尾位置、后序数组起始位置、后序数组末尾位置。
  4. 对于递归的边界条件,则当后序遍历数组为null时,返回null,当由后序遍历索引起始及末尾位置得;数组长度为1时,直接返回
  5. 对于递归的过程,则是构造中间节点,以及递归构造左右节点
  6. 同时对于如何根据后序数组,对中序数组进行分割,可以使用Map哈希表的方式,避免对中序数组进行反复查询。

实现代码如下:

class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
		if (postorder == null)
			return null;	// 边界条件
		// 构造哈希表
		Map<Integer, Integer> inMap = new HashMap<>();
		for (int i = 0; i < inorder.length; i++) {
			inMap.put(inorder[i], i);
		}
		return doBuildTree(inorder, postorder, inMap, 0, inorder.length-1, 0, postorder.length-1);
    }
	private TreeNode doBuildTree(int[] inorder, int[] postorder, Map<Integer, Integer> inMap, int inS, int inE, int postS, int postE) {
		if (inE < 0 || postE < 0 || inS > inE || postS > postE || inS >= inorder.length || postS >= postorder.length) // 考虑边界问题
			return null;
		// 根据后序遍历数组 末尾索引 获取该子树根节点值
		int rootValue = postorder[postE];
		TreeNode node = new TreeNode(rootValue);	// 构造二叉树
		if (postS == postE)		// 若此时后序数组 起始索引和末尾索引相等 说明为叶子节点
			return node;	// 直接返回
		// 根据根节点值 对中序数组进行分割 获取分割位置索引
		int index = inMap.get(rootValue);
		// 递归获取左右子树
		node.left = doBuildTree(inorder, postorder, inMap, inS, index-1, postS, postS+index-1-inS);
		node.right = doBuildTree(inorder, postorder, inMap, index+1, inE, postS+index-inS, postE-1);
		return node;
	}
}

提交结果如下:

解答成功:
执行耗时:2 ms,击败了62.35% 的Java用户
内存消耗:43.5 MB,击败了13.55% 的Java用户

复杂度分析:

  • 时间复杂度: O ( n + m ) O(n+m) O(n+m),需要遍历数组
  • 空间复杂度: O ( n + m ) O(n+m) O(n+m),考虑递归对空间的消耗

优化解法一

思路分析:

  1. 通过对解法一代码的执行流程,发现递归函数doBuildTree中的inorder参数可以省略
  2. 且对于doBuildTree函数中的边界问题判断,由于初始inEPostE均为len-1inSpostS初始为0,因此对于inE < 0的判断与inS >= inorder.length的判断包含在inS > inE中,可省略

实现代码如下:

class Solution {
	public TreeNode buildTree(int[] inorder, int[] postorder) {
		if (postorder == null)
			return null;	// 边界条件
		// 构造哈希表
		Map<Integer, Integer> inMap = new HashMap<>();
		for (int i = 0; i < inorder.length; i++) {
			inMap.put(inorder[i], i);
		}
		return doBuildTree(postorder, inMap, 0, inorder.length-1, 0, postorder.length-1);
	}
	private TreeNode doBuildTree(int[] postorder, Map<Integer, Integer> inMap, int inS, int inE, int postS, int postE) {
		if (inS > inE || postS > postE) // 考虑边界问题
			return null;
		// 根据后序遍历数组 末尾索引 获取该子树根节点值
		int rootValue = postorder[postE];
		TreeNode node = new TreeNode(rootValue);	// 构造二叉树
		if (postS == postE)		// 若此时后序数组 起始索引和末尾索引相等 说明为叶子节点
			return node;	// 直接返回
		// 根据根节点值 对中序数组进行分割 获取分割位置索引
		int index = inMap.get(rootValue);
		// 递归获取左右子树
		node.left = doBuildTree(postorder, inMap, inS, index-1, postS, postS+index-1-inS);
		node.right = doBuildTree(postorder, inMap, index+1, inE, postS+index-inS, postE-1);
		return node;
	}
}

提交结果如下:

解答成功:
执行耗时:2 ms,击败了62.35% 的Java用户
内存消耗:43.6 MB,击败了10.93% 的Java用户

复杂度分析:

  • 时间复杂度: O ( n + m ) O(n+m) O(n+m),遍历中序数组和后序数组
  • 空间复杂度: O ( n + m ) O(n+m) O(n+m),考虑每层递归传递参数对空间消耗。

解法二(递归+分治+Map)

思路分析:

  1. 跟据官方题解,将中序数组、后序数组,以及提交查询的Map变量,均改为全局遍历,即不需要作为递归函数参数,可在递归函数内访问。
  2. 因为后序遍历中,最后一个元素为子树的根节点,所以先递归获取右子树,再递归获取左子树

实现代码如下:

class Solution {
	int[] inorder;		// 中序遍历数组
	int[] postorder;	// 后序遍历数组
	Map<Integer, Integer> inMap;	// 中序遍历数组 索引表
	int postIndex;
	public TreeNode buildTree(int[] inorder, int[] postorder) {
		if (postorder == null)
			return null;	// 边界条件
		this.inorder = inorder;
		this.postorder = postorder;
		postIndex = postorder.length-1;
		// 构造哈希表
		inMap = new HashMap<>();
		for (int i = 0; i < inorder.length; i++) {
			inMap.put(inorder[i], i);
		}
		return doBuildTree(0, inorder.length-1);
	}
	private TreeNode doBuildTree(int inLeft, int inRight) {
		if (inLeft > inRight)	// 说明此时为空树
			return null;
		int value = postorder[postIndex];	// 根据postIndex 来确定当前子树 中节点值
		TreeNode node = new TreeNode(value);
		// 根据 中间节点值 获取分割中序数组索引
		int index = inMap.get(value);
		postIndex--;	// 移动所指向的根节点
		// 先获取右子树
		node.right = doBuildTree(index+1, inRight);
		// 再获取左子树
		node.left = doBuildTree(inLeft, index-1);
		return node;
	}
}

提交结果如下:

解答成功:
执行耗时:1 ms,击败了99.58% 的Java用户
内存消耗:43.2 MB,击败了32.11% 的Java用户

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n)n表示树的节点个数
  • 空间复杂度: O ( n ) O(n) O(n),需要使用 O ( n ) O(n) O(n)的空间存储哈希表,同时 O ( h ) O(h) O(h)的空间进行递归(即二叉树的高度),且h < n
  • 36
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值