题解 | #重建二叉树#
重建二叉树
http://www.nowcoder.com/practice/8a19cbe657394eeaac2f6ea9b0f6fcf6
题目描述
给定某二叉树的前序遍历和中序遍历,请重建出该二叉树并返回它的头结点。
方法一: 递归
解题思路
前序遍历的第一个节点就是这颗树的根节点。
在中序遍历中,找到这个根节点,这个根节点的左边元素就是它的左子树上的元素,这个根节点的右边元素就是它的右子树上的元素。
把范围划分好,直到范围上只有一个元素直接生成返回即可。
需要找到根节点在中序遍历上的下标,才能知道它的左子树上有多少个元素,右子树上有多少个元素。为了方便找到下标,可以用 Map 结构来存储 中序遍历数组上 所有元素和其下标 的对应关系。
以下是以 前序遍历为:[8,3,6,7,9] , 中序遍历为:[3,6,8,9,7] 的演示图:
复杂度分析
时间复杂度:O(N*logN)
需要挨个递归遍历每个元素,总共有 N 个元素;
另外在遍历每个元素的时候,用到了 HashMap 的查找操作,虽然可以认为他的查询操作一般情况下是 O(1) 级别的时间复杂度。但底层可能是链表存储,这样的查询操作的时间复杂度为 O(N);也可能是红黑树存储,这样的查询操作的时间复杂度为 O(logN);(采用哪种存储结构和存储的数量有关)
而这里 Map 的结果是有序的,数据量为 N 也不好确定具体是多少,所以最后的时间复杂度为:O(N*logN)。
空间复杂度:O(N)
重建整颗二叉树需要建立每一个节点,总共有 N 个节点,所以空间复杂度为 O(N)
代码示例
import java.util.*;
/**
* Definition for binary tree
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Solution {
public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
// 判断特殊情况
if (pre == null || pre.length == 0 || in == null || in.length == 0) {
return null;
}
// key : 中序遍历元素的值;
// value : 中序遍历元素的值 对应的 下标
// 可以以O(1)的时间复杂度找到元素在中序遍历中对应的下标
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < in.length; i++) {
map.put(in[i], i);
}
// 返回在 pre[0...pre.length-1] , in[0, in.length-1] 范围上建立二叉树的根节点
return process(pre, 0, pre.length - 1,
0, in.length - 1,
map);
}
/**
* @param pre 前序遍历的数组
* @param pL 使用到的 前序遍历的数组 的左边界,闭区间
* @param pR 使用到的 前序遍历的数组 的右边界,闭区间
* @param inL 使用到的 中序遍历的数组 的左边界,闭区间
* @param inR 使用到的 中序遍历的数组 的右边界,闭区间
* @param map 方便找到元素在中序遍历中对应的下标 需要的 数据结构
* @return 在使用到的 前序遍历数组、中序遍历数组中;生成该范围内的二叉树,返回这个二叉树的 根节点
*/
public TreeNode process(int[] pre, int pL, int pR,
int inL, int inR,
HashMap<Integer, Integer> map) {
// 越界情况,没有二叉树存在,直接返回 null
if (pL > pR || inL > inR) {
return null;
}
// 只有一个元素,就是根节点,直接返回
if (pL == pR || inL == inR) {
return new TreeNode(pre[pL]);
}
// 前序遍历的第一个元素就是 根节点
TreeNode root = new TreeNode(pre[pL]);
// 找到 根节点在 中序遍历数组里的 下标
int inIndex = map.get(pre[pL]);
// 在有效的中序遍历范围里, 根节点左边的元素 都是属于 根节点的左子树
int leftTreeSize = inIndex - inL;
// 在左子树的有效范围里,生成左子树
root.left = process(pre, pL + 1, pL + leftTreeSize,
inL, inIndex - 1,
map);
// 在右子树的有效范围里,生成右子树
root.right = process(pre, pL + leftTreeSize + 1, pR,
inIndex + 1, inR,
map);
return root;
}
} 方法二: 用栈
解题思路
虽然递归方法都可以改写成为非递归的方法实现,但还是要具体问题具体分析。
就该题来说,先了解前序遍历和中序遍历的特点:
前序遍历: 顺序就是所有第一次访问到根节点时的顺序,
如果一个元素有左子树,那么下一个元素的值就是它的左子树的值;
如果没有左子树,就看之前访问的哪个元素有右子树,那么下一个元素的值就是“之前访问有右子树的元素”的那个右子树的值。
中序遍历:顺序就是所有第二次访问到根节点时的顺序。
我们可以结合中序遍历的结果,进而找到具体的哪个元素有右子树,具体思路参照代码流程及注释。
所以,整个流程就是遍历一遍前序遍历的数组,生成相对应的左子树;再结合中序遍历,生成相对应的右子树。
以下是以 前序遍历为:[8,3,6,7,9] , 中序遍历为:[3,6,8,9,7] 的演示图:
复杂度分析
时间复杂度: O(N)
整个的大流程就是一个 for 循环遍历前序数组,虽然里面有一个 while 循环,但这个 while 循环极端情况下只会发生在前序遍历的某一个元素上,并不是所有元素都会进入 while 循环中,所有时间复杂度为 O(N)。
空间复杂度:O(N)
重建整颗二叉树需要建立每一个节点,总共有 N 个节点,所以空间复杂度为 O(N)
示例代码
import java.util.*;
/**
* Definition for binary tree
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
// 判断特殊情况
if (pre == null || pre.length == 0 || in == null || in.length == 0) {
return null;
}
Stack<TreeNode> stack = new Stack<>();
int preIndex = 0;
int inIndex = 0;
// 整颗树的根节点
TreeNode root = new TreeNode(pre[preIndex]);
stack.push(root);
// 遍历 前序遍历的数组, 生成对应的根节点
for (preIndex = 1; preIndex < pre.length; preIndex++) {
TreeNode node = stack.peek();
// 之前压栈的元素 不等于 中序遍历数组中开头的数,
// 即 之前压栈的元素 有左子树
if (node.val != in[inIndex]) {
// 再生成相应的左子树并压栈
node.left = new TreeNode(pre[preIndex]);
stack.push(node.left);
} else {
// 之前压栈的元素的值 等于 中序遍历数组中开头的数,
// 即 之前压栈的元素 没有左子树; 继续找那个元素有右子树
while (!stack.isEmpty() && stack.peek().val == in[inIndex] ) {
node = stack.pop();
inIndex++;
}
// 再生成相应的右子树并压栈
node.right = new TreeNode(pre[preIndex]);
stack.push(node.right);
}
}
// 返回整棵树的根节点
return root;
}
}
查看12道真题和解析