题目描述
给定一棵有n个结点的二叉树的先序遍历与后序遍历序列,求其中序遍历序列。
若某节点只有一个子结点,则此处将其看作左儿子结点
示例1
输入:5,[3,2,1,4,5],[1,5,4,2,3]
返回值:[1,2,5,4,3]
题目分析
在给定两个遍历序列,求另一种遍历序列中:
①给定先序序列和中序序列,可以唯一确定树,后序遍历唯一;
②给定后序序列和中序序列,可以唯一确定树,先序遍历唯一;
③给定先序序列和后序序列,不能唯一确定树;
前两种情况,能唯一确定树的原因是能够根据两个序***定根结点的左右子树范围(主要是中序序列分隔了左右子树);
第三种情况,不能确定左右子树的范围,因为当某结点只有一个子节点时,无法确定它是左子节点还是右子节点,如图,两种情况的先序和后序是相同的。
当题目中表明,只有一个结点则看做左子节点,那就可以唯一确定树了。
当先序遍历i还未到末尾时,则将i作为根节点,i+1一定是它的左子节点(若存在子节点,首先考虑左子节点),根据左子节点 i+1的值,可以在后序遍历(左-右-根)中,获得左右子树的范围:
解题思路
方法1:重建二叉树,再进行中序遍历
重建二叉树的过程,就是确定根的左右子节点的过程,然后再继续遍历左右子节点,基本思路:
1.若存在子节点一定为左子节点,所以先序遍历中,根结点i后面若还有数,则i+1一定是左子节点;
2.根据i+1的值,在后序遍历中找到左子节点的位置 j ,然后确定左子树长度和右子树长度;
3.这里一定要限制每一次递归的数组范围,所以设置pl 和 pr,sl和sr来记录递归范围;
4.当在后序遍历中找到了左子节点的位置 j ,则在后序遍历中 sl ~ j是左子树范围,j+1 ~ sr是右子树范围,同时也可以知道在先序遍历中的范围(知道左右子树的长度了),pl+1 ~ pl+1+j-sl是左子树范围,pl+1+j-sl+1 ~ pr是右子树范围;
5.最后对子树进行同样的递归遍历,直到所有的都遍历完成,对数进行中序遍历得到结果。
方法2:不创建树,直接在遍历过程中,获取中序遍历序列
在方法1中,我们需要不断创建树结点,最后再重新遍历一遍树,才能得到结果,其实可以在遍历的过程中,直接使用结果数组记录遍历的值,因为是中序遍历,所以需要在所以的左子节点都建立完成后,才将值赋给res数组,这里需要使用全局变量index,来标记当前记录的数据个数。
代码实现
方法1重建二叉树,再进行中序遍历
class TreeNode{ TreeNode left; TreeNode right; int val; TreeNode(int val){ this.val = val; } } int index=0; public int[] solve (int n, int[] pre, int[] suf) { // write code here // 重建二叉树 // 可以根据先序(根、左、右),后序(左、右、根) TreeNode root = buildTree(pre, 0, n-1, suf, 0, n-1); int[] res = new int[n]; // 进行中序遍历 midDfs(root, res); return res; } public TreeNode buildTree(int[] pre, int pl, int pr, int[] suf, int sl, int sr){ // 超过范围返回空 if(sl > sr) return null; // 将后序遍历的最后一个元素作为根节点 TreeNode root = new TreeNode(suf[sr]); for(int i=sl;i<=sr;i++){ // 在后序遍历中,找到根的左子节点pl+1 if(pl<pr && suf[i] == pre[pl+1]){ // 确定左子树范围 root.left = buildTree(pre, pl+1, pl+1+i-sl, suf, sl, i); // 确定右子树范围 root.right = buildTree(pre, pl+2+i-sl, pr, suf, i+1, sr-1); break; } } return root; } public void midDfs(TreeNode root, int[] res){ if(root == null) return; midDfs(root.left, res); res[index++] = root.val; midDfs(root.right, res); }
时间复杂度:,在后序遍历中找左子节点最坏需要n次,而递归深度最多为n,所以时间花费最多为n^2;
空间复杂度:,需要使用额外n空间的递归栈以及创建树所需要的n的空间;
方法2:不创建树,直接在遍历过程中,获取中序遍历序列
int index=0; int[] res; public int[] solve (int n, int[] pre, int[] suf) { // 可以直接在建树的过程中,给数组赋值 res = new int[n]; buildTree(pre, 0, n-1, suf, 0, n-1); return res; } public void buildTree(int[] pre, int pl, int pr, int[] suf, int sl, int sr){ if(sl > sr) return ; if(sl == sr){ // 碰到子树只有一个值,则直接记录下来 res[index++] = suf[sr]; return; } for(int i=sl;i<=sr;i++){ // 在后序遍历中找到左子节点的位置i if(pl<pr && suf[i] == pre[pl+1]){ // 获取左子树范围(后序:sl~i) buildTree(pre, pl+1, pl+1+i-sl, suf, sl, i); // 中序遍历(先左子节点) res[index++] = suf[sr]; // 获取右子树范围(后序:i+1~sr-1) buildTree(pre, pl+2+i-sl, pr, suf, i+1, sr-1); break; } } }
时间复杂度:,在后序遍历中找左子节点最坏需要n次,而递归深度最多为n,所以时间花费最多为n^2;
空间复杂度:,需要使用额外n空间的递归栈,这里可以不用创建树;