题目:
给定两个数组,其中元素为一颗树用先序遍历和中序遍历的结果。利用两个数组,来构建一颗新树。
假设二叉树如上图。
先序数组:头左右的顺序 [1,2,4,5,3,6,7]
中序数组:左头右的顺序 [4,2,5,1,6,3,7]
递归函数f:由先序数组pre、数组范围0 ~ 6和中序数组in 数组范围 0 ~ 6 构建新的树。并返回head节点 f(pre,0,6,in,0,6)
分析:
- 新树的head节点是谁? 当然是先序数组的第一个元素。
- 确定了head节点后,左树怎么建?
先序返回的顺序是,头左右,中序返回的顺序是左头右,所以确定了先序数组的第一个元素为新树的head后,那么它后面的3个节点 加上 根据新树head节点在中序中找到对应节点(head节点在中序数组中的位置)的左端,就是新树的左侧节点。如果调用递归函数的话,参数就应该是f(pre,1,3,in,0,2),返回后的结果,则是新树的左侧节点。 - 右数如何建立?
上一步操作已经确定了左侧节点,那先序数组4 ~ 6以及中序数组的 4 ~ 6的索引位置,就是新树的右侧节点。调用f(pre,4,6,in,4,6)函数构建后返回即可。 - 给定两个数组中,如果有一个为null,则不需构建新树。
- 如果元素中仅有一个元素,说明二叉树中只有一个节点,那么先序和中序是一样的,直接构建新树后return。
- 如果先序数组长度和中序数组长度不相同,同样return。
- 如何确定左右节点长度范围?由于已经确定了先序数组中第一个元素为新树head节点
7.1 根据先序head节点,找到中序数组中该节点位置,find。
7.2 先序数组中head节点 + find - 中序数组中前面元素的数量 ,就可以确定了先序数组和中序数组左右侧的范围。
7.3 举例:先序数组位置从 5,6,7,8,9,10 中序数组位置从 13,14,15,16,17,18 一样长度6个元素
如果,此时find位置在16的元素和先序数组中5位置元素相同,那16左侧13 ~ 15 3个位置就应该是左侧节点,那先序数组左侧 就应该是 16 - 13 + 5 = 8,所以先序数组 6 ~ 8的位置就应该是左侧节点。
确定了左右侧树的节点后,递归调用f函数,调整数组范围参数即可。
代码实现:
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int val) {
this.val = val;
}
}
public static TreeNode buildTree(int[] pre, int[] in) {
if (pre == null || in == null || pre.length != in.length) {
return null;
}
return f(pre, 0, pre.length - 1, in, 0, in.length - 1);
}
//一棵树,先序数组 [L1 ... R1] 中序数组 [L2 ... R2]
//构建新树,返回head节点
public static TreeNode f(int[] pre, int L1, int R1, int[] in, int L2, int R2) {
//如果树的结构是 1的right是2 2的right是3,
//那么先序结果就是[1,2,3] 中序的结果也是[1,2,3]
//那么在中序数组找到find是第一个位置,左侧没东西了
//或者 结构是 1的left是2 2的right是3
//先序[1,2,3] 中序 [2,3,1] 中序的右边没东西了,数组越界。
if (L1 > R1) {
return null;
}
TreeNode head = new TreeNode(pre[L1]);
//说明长度为1,数组内只有一个元素
if (L1 == R1) {
return head;
}
int find = L2;
//找出中序中的find节点,确认左右树范围
while (in[find] != pre[L1]) {
find++;
}
head.left = f(pre, L1 + 1, L1 + find - L2, in, L2, find - 1);
head.right = f(pre, L1 + find - L2 + 1, R1, in, find + 1, R2);
return head;
}
public static void pre(TreeNode node, List preList) {
if (node == null) {
return;
}
preList.add(node.val);
pre(node.left,preList);
pre(node.right,preList);
}
public static void in(TreeNode node, List inList) {
if (node == null) {
return;
}
pre(node.left,inList);
inList.add(node.val);
pre(node.right,inList);
}
public static void printTree(TreeNode node){
if (node == null) {
return;
}
System.out.println(node.val);
printTree(node.left);
printTree(node.right);
}
public static void main(String[] args) {
TreeNode node = new TreeNode(1);
node.left = new TreeNode(2);
node.right = new TreeNode(3);
node.left.left = new TreeNode(4);
node.left.right = new TreeNode(5);
node.right.left = new TreeNode(6);
node.right.right = new TreeNode(7);
List<Integer> preList = new ArrayList();
List<Integer> inList = new ArrayList();
pre(node,preList);
in(node,inList);
int[] preArr = preList.stream().mapToInt(i -> i).toArray();
int[] inArr = inList.stream().mapToInt(i -> i).toArray();
TreeNode treeNode = buildTree(preArr, inArr);
printTree(treeNode);
}
部分代码优化:之前代码都会在每一次递归中遍历的寻找find,那直接将中序数组中的值放进map中,从map中获取即可。
public static TreeNode buildTree(int[] pre, int[] in) {
if (pre == null || in == null || pre.length != in.length) {
return null;
}
HashMap<Integer, Integer> valueIndex = new HashMap<>();
for (int i = 0; i < in.length; i++) {
valueIndex.put(in[i],i);
}
return f(pre, 0, pre.length - 1, in, 0, in.length - 1,valueIndex);
}
//一棵树,先序数组 [L1 ... R1] 中序数组 [L2 ... R2]
//构建新树,返回head节点
public static TreeNode f(int[] pre, int L1, int R1, int[] in, int L2, int R2, HashMap<Integer, Integer> valueIndex) {
//如果树的结构是 1的right是2 2的right是3,
//那么先序结果就是[1,2,3] 中序的结果也是[1,2,3]
//那么在中序数组找到find是第一个位置,左侧没东西了
//或者 结构是 1的left是2 2的right是3
//先序[1,2,3] 中序 [2,3,1] 中序的右边没东西了,数组越界。
if (L1 > R1) {
return null;
}
TreeNode head = new TreeNode(pre[L1]);
//说明长度为1,数组内只有一个元素
if (L1 == R1) {
return head;
}
int find = valueIndex.get(L1);
head.left = f(pre, L1 + 1, L1 + find - L2, in, L2, find - 1,valueIndex);
head.right = f(pre, L1 + find - L2 + 1, R1, in, find + 1, R2,valueIndex);
return head;
}
总结:
- 递归算法如果条理不清晰,可先将先序、中序等整体步骤,每个节点经过几次画出来。
- 上述代码,第一次1是head,并找到左右分界范围。
- 第二次head.left时,根据范围会查找左侧的head和right的head,以此类推。