方法一:递归,从根结点出发,将根结点下的每一个结点都看作是根结点(递归的思想),然后根据前序遍历的特性,遍历结果的第一个值就是根结点的值,因此可以确定递归的赋值过程,接下来就是确认书的前序遍历和中序遍历的位置,分别对(左子树的前序遍历,左子树的中序遍历),(右子树的前序遍历,右子树的中序遍历)进行递归构建二叉树。这里还有一点特别重要,就是要通过前序遍历中的根结点的值找到中序遍历中根结点的值所在的索引,以便用来划分中序遍历中的左子树和右子树。
/**
* 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 index = getIndex(preorder,inorder);
//注意copyOfRange()是左闭右开区间
root.left = buildTree(Arrays.copyOfRange(preorder,1,index+1),Arrays.copyOfRange(inorder,0,index));
root.right = buildTree(Arrays.copyOfRange(preorder,index+1,preorder.length),Arrays.copyOfRange(inorder,index+1,inorder.length));
return root;
}
public int getIndex(int[] preorder,int[] inorder){
int index = 0;
for(int i = 0;i<inorder.length;i++){
if(inorder[i] == preorder[0]){
index = i;
}
}
return index;
}
}
时间复杂度:O(n),树结点的个数
空间复杂度:O(1)
方法二:使用迭代法
什么是迭代法,迭代法就是使用非递归的形式对二叉树进行遍历,和递归式一样,迭代式也分为前序、中序、后序三种迭代方式,迭代法具体的方法是,使用一个栈来暂时存放二叉树中的结点,同时使用一个result数组来存放最终的遍历结果。
使用迭代法的方式实现二叉树的前序遍历(根左右)
public List<Integer> preorderTraversal(TreeNode root){
Deque<TreeNode> stack = new LinkedList<TreeNode>();
if(root == null){return result;}
List<Integer> result = new ArrayList<Integer>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode node = stack.peek();
result.add(node.val);
if(node.right!=null){stack.push(node.right);}
if(node.left!=null){stack.push(node.left);}
}
return result;
}
迭代法在这道题中的应用:使用一个栈来对前序遍历中的元素暂存,当需要构建左子树的时候栈顶元素有一个特征就是,栈顶元素和inorder(index)的只是不相等的,因此有下面的思路。
- 从1遍历一遍preorder,【每次比较栈顶元素和inorder[index] 是否相等】,
- 如果不等:preorder[i] 入栈且将其变为左子树;
- 如果相等:弹出栈顶元素,index + 1;
- 就这样一直弹出直到栈顶元素和 inorder[index] 不等时,将 preorder[I] 变为右子树。(说明到了一个左子树的最左边)
/**
* 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.length == 0|| inorder.length == 0){
return null;
}
//构建根节点
TreeNode root = new TreeNode(preorder[0]);
//使用栈作为辅助
Deque<TreeNode> stack = new LinkedList<TreeNode>();
//初始化栈,将根结点入栈
stack.push(root);
//中序遍历的遍历索引
int index = 0;
//从1开始遍历前序遍历数组
for(int i = 1;i < preorder.length;i++){
//取出栈顶元素
TreeNode node = stack.peek();
//当栈顶结点和inorder索引index所指的值不相等的时候,此时preorder[i]为栈顶结点的左孩子结点
if(node.val != inorder[index]){
node.left = new TreeNode(preorder[i]);
stack.push(node.left);
}else{
//当栈顶结点和inorder索引index所指的值相等的时候,说明已经到了最左边了,因此将栈顶结点一直弹出,让出位置用来构建右节点,同时移动inorder的索引,直到栈顶结点的值和inorder[index]不相等时,可以开始构建右节点
while(!stack.isEmpty() && stack.peek().val == inorder[index]){
node = stack.pop();
index++;
}
//
node.right = new TreeNode(preorder[i]);
stack.push(node.right);
}
}
return root;
}
}
方法三:递归+散列优化
class Solution {
private Map<Integer, Integer> indexMap;
public TreeNode myBuildTree(int[] preorder, int[] inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
if (preorder_left > preorder_right) {
return null;
}
// 前序遍历中的第一个节点就是根节点
int preorder_root = preorder_left;
// 在中序遍历中定位根节点
int inorder_root = indexMap.get(preorder[preorder_root]);
// 先把根节点建立出来
TreeNode root = new TreeNode(preorder[preorder_root]);
// 得到左子树中的节点数目
int size_left_subtree = inorder_root - inorder_left;
// 递归地构造左子树,并连接到根节点
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
root.left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
// 递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
root.right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
return root;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
int n = preorder.length;
// 构造哈希映射,帮助我们快速定位根节点
indexMap = new HashMap<Integer, Integer>();
for (int i = 0; i < n; i++) {
indexMap.put(inorder[i], i);
}
return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
}
}
用哈希表建立映射,空间换时间,如果是使用方法一的方式,每次递归传入的是两个新的前序遍历和中序遍历数组,在获取index的时候有些不太方便,因为获取到的index是新preorder[0]在旧inorder中的索引,而我们需要的是新索引,本人能力不足,暂时没想到如何在法一基础上建立map索引,如果有大佬想到了,欢迎在评论区留言,感谢!!!
方法四:迭代法递归化
实际上第四种方法是对思路三的递归形式,我们没有必要有一个辅助栈来维护父节点,只有保证递归顺序是先左后右,再将父亲节点的值作为参数传入即可,该思路比思路三写起来更为简洁,对递归的理解也更为深入
class Solution {
private int in = 0;
private int pre = 0;
private int[] preorder;
private int[] inorder;
public TreeNode buildTree(int[] preorder, int[] inorder) {
this.preorder = preorder;
this.inorder = inorder;
return build(Integer.MIN_VALUE);
}
private TreeNode build(int stop) {
if (pre >= preorder.length)
return null;
if (inorder[in] == stop) {
in++;
return null;
}
TreeNode node = new TreeNode(preorder[pre++]);
node.left = build(node.val);
node.right = build(stop);
return node;
}
}
方法四作者:yun-yu-chen
链接:https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/solution/si-chong-si-lu-shen-ru-yu-qian-chu-di-gu-53eh/