Note:《剑指offer》面试题7 重建二叉树
题目:给出了前序和中序周游序列,根据这两个序列重建一棵二叉树
关键点:前序周游时,根节点为序列的第一个值;中序周游时,根节点左边的序列是根节点的左子树节点,右边是右子树节点
第1、2种方法思路一致,只不过用了Map以提高检索父节点和子节点index的效率。
思路:遍历除了第一个值的前序周游子序列,将所有其他值和根节点值对比,通过在中序周游序列中它们的index判断(节点index先于根节点index,则为其左子树,反之右子树,这个判断由中序周游的特点决定的)。节点在根节点在其左子树或右子树,如果根节点没有左儿子或者右儿子节点,则该节点成为其子节点;否则,节点将要对比的新根节点变成原根节点的儿子节点,直到它成为某个对比的根节点的子节点。 时间复杂度应该是O(n*h),n为节点数,h为树的高度。
第一种方法,超时因为indexOf
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder == null || preorder.length == 0){
return null;
}
TreeNode root = new TreeNode(preorder[0]);
TreeNode parent = root;
// 从先序周游开始
for(int i = 1; i < preorder.length; i++){
TreeNode node = new TreeNode(preorder[i]);
while(true){
boolean isLeft = isLeftSubTree(inorder, parent.val, node.val);
if(isLeft){ // 如果是左子树
if(parent.left == null){ // 左子树为空,则它为父节点的左儿子
parent.left = node;
break;
}else{ // 左子树有自己的根节点,则将和其对比
parent = parent.left;
}
}else{
if(parent.right == null){
parent.right = node;
break;
}else{
parent = parent.right;
}
}
}
parent = root;
}
return root;
}
/** 节点是root节点的左子树还是右子树 **/
private boolean isLeftSubTree(int[] inorder, int root, int node) {
int rootIndex = indexOf(inorder, root);
int nodeIndex = indexOf(inorder, node);
// 如果节点在中序周游的索引位于根节点的后面,表示它在根节点的右子树
if(nodeIndex > rootIndex){
return false;
}else if(nodeIndex < rootIndex){ // 反之
return true;
}else{
return false;
} // 按理来说不存在
}
/** 找到某数在数组中的索引 **/
private int indexOf(int[] array, int elem){
for (int i = 0; i < array.length; i++) {
if (elem == array[i])
return i;
}
return -1;
}
第二种方法,103ms
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder == null || preorder.length == 0){
return null;
}
TreeNode root = new TreeNode(preorder[0]);
TreeNode parent = root;
Map<Integer, Integer> inorderIndexMap = buildIndexMap(inorder);
// 从先序周游开始
for(int i = 1; i < preorder.length; i++){
TreeNode node = new TreeNode(preorder[i]);
while(true){
boolean isLeft = isLeftSubTree(inorderIndexMap, parent.val, node.val);
if(isLeft){
if(parent.left == null){
parent.left = node;
break;
}
else{
parent = parent.left;
}
}else{
if(parent.right == null){
parent.right = node;
break;
}
else{
parent = parent.right;
}
}
}
parent = root;
}
return root;
}
private boolean isLeftSubTree(Map<Integer, Integer> inorderIndexMap, int root, int node) {
int rootIndex = inorderIndexMap.get(root);
int nodeIndex = inorderIndexMap.get(node);
// 如果节点在中序周游的索引位于根节点的后面,表示它在根节点的右子树
if(nodeIndex > rootIndex){
return false;
}else if(nodeIndex < rootIndex){ // 反之
return true;
}else{
return false;
} // 按理来说不存在
}
/** 用不重复的数组建其值与index的map **/
private Map<Integer, Integer> buildIndexMap(int[] array){
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
int index = 0;
for(int x : array){
map.put(x, index);
index++;
}
return map;
}
第3种,方法看了《剑指offer》面试题7——重建二叉树的思路后实现的
思路:在树的前序遍历中,根节点为第一个,后面连续的一部分子序列为左子树节点,左子树序列后面连续的子序列为右子树节点。在中序遍历中,根节点以左序列为根节点的左子树,以右序列为右子树节点。那么,在前序周游序列中得到根节点值,可以通过该值在中序周游序列中找到其index,那么index之前为左子树,通过计算能得到左右子树节点数。然后,重建左右子树,用递归的方法重建整个二叉树。 时间复杂度O(n)
第三种方法,6ms
private Map<Integer, Integer> inorderIndexMap = null;
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder == null || preorder.length == 0){
return null;
}
inorderIndexMap = buildIndexMap(inorder);
return buildTree(preorder.length, preorder, 0, inorder, 0);
}
private TreeNode buildTree(int len, int[] preorder, int pStart,
int[] inorder, int iStart) {
if(len <= 0){
return null;
}
TreeNode root = new TreeNode(preorder[pStart]); // 在preorder中第pStart个为子树的root
int rootIndex = inorderIndexMap.get(root.val); //找到inorder中子树root的位置
int leftLen = rootIndex-iStart;
root.left = buildTree(leftLen, preorder, pStart+1, inorder, iStart);
root.right = buildTree(len-1-leftLen, preorder, pStart+1+leftLen, inorder, rootIndex+1);
return root;
}
/** 用不重复的数组建其值与index的map **/
private Map<Integer, Integer> buildIndexMap(int[] array){
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
int index = 0;
for(int x : array){
map.put(x, index);
index++;
}
return map;
}