描述
题目描述
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
示例
输入:[1,2,3,4,5,6,7],[3,2,4,1,6,5,7] 返回值:{1,2,5,3,4,6,7}
知识点:二叉树,递归,分治,队列
难度:⭐⭐⭐
题解
解题思路
对于这样类似要左右区间分开求出结果的类型的二叉树问题,一般可以用递归
这道理最关键的是:了解前需每次递归求出中序遍历数组中,左区间或右区间的的根节点在中序遍历数组的
其中左右分别递归区间,分别得到左右区间的根节点作为上次根节点的左右子结点,采用了类似分治的思想
除了递归,我们还可以通过队列或栈实现非递归解法
方法一:递归
图解
算法流程:
- 递归函数功能定义:根据前序和中序遍历获取当前递归的根节点
- 递归终止条件:前序遍历数组长度为1,直接返回,表示已经递归到最右叶子节点
- 前序遍历的首元素就是当前递归层次的根节点
- 确定左右子树的范围 ,从中间向两边查找跟节点索引,从中序遍历中根据根节点的值找到中序遍历数组中根节点的索引
- 当前根节点的左子节点为左子树的跟节点,拼接、构建二叉树即可
Java 版本代码如下:
import java.util.*; public class Solution { // 递归函数功能定义:根据前序和中序遍历获取当前递归区间的根节点 public TreeNode reConstructBinaryTree(int [] pre,int [] in) { if(pre == null || pre.length == 0) { return null; } // 前序遍历的首元素就是当前递归层次的根节点 int rootVal = pre[0]; // 当前长度为1,直接返回,表示已经递归到最右叶子节点 if(pre.length == 1) { return new TreeNode(rootVal); } int rootIndex = 0; // 确定左右子树的范围 // 从中间向两边查找跟节点索引 for(int i = 0; i < in.length; i++) { if(rootVal == in[i]) { // 中序遍历数组中根节点的索引 rootIndex = i; break; } } TreeNode root = new TreeNode(rootVal); // 左右分别递归区间,分别得到左右区间的根节点作为上次根节点的左右子结点 // copyOfRange为左开右闭 [), 即包括左界限不包括右界限 root.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, rootIndex + 1), Arrays.copyOfRange(in, 0, rootIndex)); root.right = reConstructBinaryTree(Arrays.copyOfRange(pre, rootIndex + 1, pre.length), Arrays.copyOfRange(in, rootIndex + 1, in.length)); return root; } }
Go 版本代码如下:
package main import . "nc_tools" func reConstructBinaryTree( pre []int , vin []int ) *TreeNode { if len(pre)==0{ return nil } if len(pre)==1{ return &TreeNode{pre[0],nil,nil} } root:=&TreeNode{} root.Val=pre[0] index:=0 // 确定左右子树的范围 // 从中间向两边查找跟节点索引 for i:=0;i<len(pre);i++{ if vin[i]==root.Val{ index=i break } } // 左右分别递归区间,分别得到左右区间的根节点作为上次根节点的左右子结点 root.Left=reConstructBinaryTree(pre[1:index+1],vin[:index]) root.Right=reConstructBinaryTree(pre[index+1:],vin[index+1:]) return root }
复杂度分析:
时间复杂度 O(N):递归了每个结点,N为结点数
空间复杂度 O(N):递归所用的栈空间
方法二:迭代
算法流程:
- 维护一个指针:指向根节点在中序遍历中的索引位置的指针,栈用来保存结点
- 初始时栈中存放了根节点(前序遍历的第一个节点),指针指向中序遍历的第一个节点;
- 我们依次枚举前序遍历中除了第一个节点以外的每个节点。、
- 用前序数组一直构建左子树, 即一开始是先走到最左叶子结点,如果 index 和栈顶节点不同,我们将当前节点作为栈顶节点的左儿子;连接左子节点
- 如果 index 恰好指向栈顶节点,即表示到了左下角,这时就需要往上走并处理右子树,此时不断地弹出栈顶节点并向右移动 index,并将当前节点作为最后一个弹出的节点的右儿子;连接右子节点
Java 版本代码如下:
import java.util.*; public class Solution { public TreeNode reConstructBinaryTree(int[] preorder, int[] inorder) { if (preorder == null || preorder.length == 0) { return null; } TreeNode root = new TreeNode(preorder[0]); Deque<TreeNode> stack = new LinkedList<TreeNode>(); stack.push(root); // 指向根节点在中序遍历中的索引位置的指针 int inorderIndex = 0; for (int i = 1; i < preorder.length; i++) { int preorderVal = preorder[i]; TreeNode node = stack.peek(); // 用前序数组一直构建左子树, 即一开始是先走到最左叶子结点 if (node.val != inorder[inorderIndex]) { // 当前节点作为栈顶节点的左儿子结点 node.left = new TreeNode(preorderVal); stack.push(node.left); } else { // 表示到了左下角,这时就需要往上走并处理右子树 while (!stack.isEmpty() && stack.peek().val == inorder[inorderIndex]) { node = stack.pop(); // 指针往上走,直到碰到节点node有右子节点 inorderIndex++; } // 连接右子结点 node.right = new TreeNode(preorderVal); stack.push(node.right); } } return root; } }
复杂度分析:
时间复杂度 O(N):迭代每个结点
空间复杂度 O(N):栈用到额外空间