前言:
二叉树的构造是指给定一个数组,让我们通过一定的规则来恢复出二叉树的原有结构。
思路:
解决二叉树的问题可以归结于两种方式:
第一种:是否可以通过遍历一遍二叉树得到答案?如果可以,用一个traverse
函数配合外部变量来实现,这叫「遍历」的思维模式。
第二种:是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案?如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值,这叫「分解问题」的思维模式。
这两种方式都是通过递归来实现的,同时我们也可以在递归的过程当中携带一些过程变量来协助我们解决问题。
我们需要做的事情就是:在遍历每一个节点的过程需我们需要做什么,是在进入这个节点(前序遍历),在这个节点(中序遍历),还是离开这个节点(后续遍历)时做事情。至于具体执行流程我们不必过于关注,只需要控制好递归边界条件就行。
在二叉树的构造中,是通过递归来创建当前节点,然后便利左右两边的数据去构造当前节点的左子树和右子树。
经典例题:
例题1 力扣第654题
解决代码:
public class buildTree654 {
public TreeNode constructMaximumBinaryTree(int[] nums) {
return buildTree(nums, 0 ,nums.length - 1);
}
public TreeNode buildTree(int[] nums, int left, int right) {
// 找到当前接点
int index = findMax(nums, left, right);
TreeNode treeNode = new TreeNode(nums[index]);
// 构造当前接点的左子树
if (left< index) {
treeNode.left = buildTree(nums, left, index - 1);
}
// 构造当前接点的右子树
if (right > index) {
treeNode.right = buildTree(nums, index + 1, right);
}
return treeNode;
}
private int findMax(int[] nums, int left, int right) {
int max = left;
for (int i = left; i <= right ; i++) {
if (nums[i] > nums[max]) {
max = i;
}
}
return max;
}
}
根据遍历的结果图可知:
对于一颗二叉树,在前序遍历中,第一个节点为该二叉树的头接点,但无法确认后面的结点哪些是左子树,哪些是右子树。在中序遍历中,无法确认头接点,但头节点的左边为二叉树的左子树,右边为二叉树的右子树。
因此图中绿色为找到整颗二叉树的头接点。然后根据中序遍历的遍历结果可知左子树有三个节点,右子树有两个节点,然后再根据前序遍历可以确认左右子树的头结点(图中橙色节点)然后依次递归构建完成整颗二叉树。
解决代码:
public class BuildTree105 {
public TreeNode buildTree(int[] preorder, int[] inorder) {
// 通过前序遍历找当前接点,通过中序遍历找左右子树
return buildTree(preorder, inorder, 0 , preorder.length - 1, 0, inorder.length - 1);
}
public TreeNode buildTree(int[] preorder, int[] inorder, int preLeft, int preRight, int inLeft, int inRight ){
TreeNode root = new TreeNode(preorder[preLeft]);
int rootIndex = findRoot(inorder, preorder[preLeft], inLeft, inRight);
int leftLen = rootIndex - inLeft;
int rightLen = inRight - rootIndex;
// 存在左子树
if (leftLen > 0) {
root.left = buildTree(preorder, inorder, preLeft + 1, preLeft + leftLen, inLeft, rootIndex - 1);
}
// 存在右子树
if (rightLen > 0) {
root.right = buildTree(preorder, inorder, preLeft + leftLen + 1, preRight, rootIndex + 1, inRight);
}
return root;
}
private int findRoot(int[] inorder, int value, int inLeft, int inRight) {
for (int i = inLeft; i <= inRight; i++) {
if (inorder[i] == value) {
return i;
}
}
return 0;
}
}
例题3 通过二叉树的后续和中序遍历结果构造二叉树:
后序遍历相比于前序遍历相比,树的根节点变为了整个数组的最后一个节点,因此在构造子树时只需要选择最后一个节点即可,其他没有太大变化
解决代码:
public class BuildTree106 {
public TreeNode buildTree(int[] inorder, int[] postorder) {
// 通过后续遍历找当前接点,通过中序遍历找左右子树
return buildTree(inorder, postorder, 0 , inorder.length - 1, 0, postorder.length - 1);
}
public TreeNode buildTree(int[] inorder, int[] postorder, int inLeft, int inRight, int postLeft, int postRight){
TreeNode root = new TreeNode(postorder[postRight]);
int rootIndex = findRoot(inorder, postorder[postRight], inLeft, inRight);
int leftLen = rootIndex - inLeft;
int rightLen = inRight - rootIndex;
// 有左子树
if (leftLen > 0) {
root.left = buildTree(inorder, postorder, inLeft, rootIndex - 1, postLeft, postLeft + leftLen -1);
}
// 有右子树
if (rightLen > 0) {
root.right = buildTree(inorder, postorder, rootIndex + 1, inRight, postLeft + leftLen , postRight - 1);
}
return root;
}
private int findRoot(int[] inorder, int value, int inLeft, int inRight) {
for (int i = inLeft; i <= inRight; i++) {
if (inorder[i] == value) {
return i;
}
}
return 0;
}
}
例题4 通过二叉树的前序和后续遍历结果构造二叉树:
根据遍历的结果图可知:
首先明确一点,就是根据前序遍历和后续遍历的结果是不能够确认唯一一棵二叉树的,具体原因是因为后下标红的那句话,我们默认认为这棵二叉树是有左子树的。前序遍历的首个节点为根结点绿色节点,然后可以认为根结点的下一个节点是左子树的根节点(橙色),此时可以通过后续遍历确认左子树的长度是多少,进而可以确认右子树的根接点(橙色),最后进行递归构造。
解决代码:
class Solution889 {
public TreeNode constructFromPrePost(int[] preorder, int[] postorder) {
return buildTree(preorder, postorder, 0, preorder.length - 1, 0, postorder.length -1);
}
private TreeNode buildTree(int[] preorder, int[] postorder, int preLeft, int preRight, int postLeft, int postRight) {
System.out.println(preLeft + " " + preRight + " " + postLeft + " " + postRight);
TreeNode root = new TreeNode(preorder[preLeft]);
if (preLeft + 1 > preRight) {
return root;
}
int index = findNextLeftRoot(postorder, preorder[preLeft + 1], postLeft, postRight);
int leftLength = index - postLeft + 1;
int rightLength = postRight - 1 - index;
System.out.println(root.val + " " + index + " " + leftLength + " " + rightLength);
if (leftLength > 0) {
root.left = buildTree(preorder, postorder, preLeft + 1, preLeft + leftLength , postLeft, postLeft + leftLength - 1);
}
if (rightLength > 0) {
root.right = buildTree(preorder, postorder, preLeft + leftLength + 1, preRight, postLeft + leftLength, postRight - 1);
}
return root;
}
private int findNextLeftRoot(int[] postorder, int value, int postLeft, int postRight) {
for (int j = postLeft; j <= postRight ; j++) {
if (postorder[j] == value) {
return j;
}
}
return 0;
}
}