1.题目
剑指 Offer 07. 重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/
9 20
/
15 7
限制:
0 <= 节点个数 <= 5000
2.自我思路与实现
本题未自我解决
思路:因为树的遍历是个递归过程,那么重构应该也是个递归过程,因此本题的方法应当是递归,且递归应当时创建子树的过程,但具体实现不明白
3.总结思路与实现
1.递归法
前序遍历的顺序是:根节点、左子树、右子树
中序遍历的顺序是:左子树、根节点、右子树
(后序遍历的顺序是:左子树、右子树、根节点)
在前序遍历中,根节点在第一个位置上,可以由此在中序遍历中找到根节点的位置
在中序遍历中,根节点左边全是左子树,右边全是右子树,由此可以计算出左子树的节点数量和右子树的节点数量,可得到左子树的前序、中序遍历范围和右子树的前序、中序遍历范围,可通过递归的方式重建左子树和右子树,从而重建二叉树
1.使用Map将中序遍历的元素和角标对应,以便从前序遍历中的根节点值快速得到其在中序遍历中的位置
2.调用递归方法, 对应前序和中序起始和结束角标都是0和length - 1
递归方法的基准情形有三个:
1.前序遍历的起始角标大于结束,当前树为空
2.前序遍历的起始角标等于结束,当前树只有一个节点,就是根节点
3.前序遍历的起始角标大于结束,当前树有多个节点,从前序遍历得到根的值,从中序遍历得到根的角标,进而得到左子树和右子树的范围,递归重建左右子树,最后链接到根上
时间n:遍历一遍中序,每个节点创建
空间n:递归存储整棵树,哈希表
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
//特例判断
if(preorder == null || preorder.length == 0)
return null;
//获得中序遍历的元素与角标存入哈希表
Map<Integer, Integer> index = new HashMap<>();
for(int i = 0; i < inorder.length; i++)
{
index.put(inorder[i], i);
}
return buildTree(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1, index);
}
public TreeNode buildTree(int[] preorder, int preLeft, int preRight,
int[] inorder, int inLeft, int inRight,
Map<Integer, Integer> index)
{
//前序遍历开始大于结束,则树中无元素
if(preLeft > preRight)
return null;
TreeNode root = new TreeNode(preorder[preLeft]);
//前序遍历开始等于结束,树中只有一个元素
if(preLeft == preRight)
return root;
else
{
int rootIndex = index.get(preorder[preLeft]);//根在中序遍历中的角标
int leftNum = rootIndex - inLeft;//中序遍历中根的左边是左子树
int rightNum = inRight - rootIndex; //中序遍历中根的右边是右子树
TreeNode leftSubTree = buildTree(preorder, preLeft + 1, preLeft + leftNum,
inorder, rootIndex - leftNum, rootIndex - 1,
index); //左子树在前序和中序遍历之中的位置
TreeNode rightSubTree = buildTree(preorder, preLeft + leftNum + 1, preLeft + leftNum + rightNum,
inorder, rootIndex + 1, rootIndex + rightNum,
index);//右子树在前序和中序遍历之中的位置
root.left = leftSubTree;//链接左子树
root.right = rightSubTree;//链接右子树
}
return root;
}
}
2.迭代法
对于先序遍历中的两个相邻数mn(m在n前),有三种情况
一,n是m的左子树节点的值
二,n是m右子树节点的值(此时m无左子树)
三,n是m某个祖先节点的右节点(m没有子树)
反映到前序遍历 preorder = [3,9,20,15,7],从根出发,有左子节点就向左下,直到最左下角
中序遍历 inorder = [9,3,15,20,7],从最左下角向上,碰到有节点有右子节点,就转入该右子树,从左下角开始
1.创建栈,并将根节点压入栈中
2.开始遍历前序遍历,将栈顶端节点定为node,与最左下角节点值(中序遍历第一个元素)比较
相等:已经到达最左下角,向上找到存在有右子节点的节点,设为node的右子节点,循环2
不等:继续遍历前序数组,循环2
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
//特例判断
if(preorder == null || preorder.length == 0)
return null;
//建立根节点
TreeNode root = new TreeNode(preorder[0]);
//中序遍历指针
int inIndex = 0;
//创建栈并将根节点压入栈中
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
//遍历前序遍历
for(int i = 1; i < preorder.length; i++)
{
//栈顶节点
TreeNode node = stack.peek();
if(node.val != inorder[inIndex])//不断创建左子树
{
node.left = new TreeNode(preorder[i]);
stack.push(node.left);
}
else
{ //向上找到存在右子节点的节点
while(!stack.isEmpty() && stack.peek().val == inorder[inIndex])//栈不为空(保证存在根的左子节点)
//中序向上遇到存在右子节点的节点会转向
{
node = stack.pop();
inIndex++;
}
//从此节点开始重新寻找该节点的左子树
node.right = new TreeNode(preorder[i]);
stack.push(node.right);
}
}
return root;
}
}
4.相关知识
- 树的相关一般选择递归
- Stack的三个常用方法:push()压入,pop()弹出最顶端元素并返回,peek()返回最顶端元素