构造类问题思路:

labuladong的总结很好

二叉树的构造问题一般都是使用「分解问题」的思路:构造整棵树 = 根节点 + 构造左子树 + 构造右子树。

经典的构造题有三道:

654. 最大二叉树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public TreeNode constructMaximumBinaryTree(int[] nums) {
// 使用分解问题的思路解题,构造顺序为 根节点 + 构造左树 + 构造的右树
TreeNode root = constructTree(nums);
return root;
}

private TreeNode constructTree(int[] nums) {
if (nums.length == 0) return null;
int maxIdx = findMaxNumIndex(nums);
TreeNode root = new TreeNode(nums[maxIdx]);
// Arrays.copyOfRange 时间复杂度是O(N)
root.left = constructTree(Arrays.copyOfRange(nums, 0, maxIdx));
root.right = constructTree(Arrays.copyOfRange(nums, maxIdx + 1, nums.length));
return root;
}

private int findMaxNumIndex(int[] nums) {
int idx = 0;
int maxValue = Integer.MIN_VALUE;
for (int i = 0; i < nums.length; i++) {
if (nums[i] > maxValue) {
maxValue = nums[i];
idx = i;
}
}
return idx;
}

⚠️由于使用了Arrays.copyOfRange此方法会有些慢

优化后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* 主函数 */
public TreeNode constructMaximumBinaryTree(int[] nums) {
return build(nums, 0, nums.length - 1);
}

/* 定义:将 nums[lo..hi] 构造成符合条件的树,返回根节点 */
TreeNode build(int[] nums, int lo, int hi) {
// base case
if (lo > hi) {
return null;
}

// 找到数组中的最大值和对应的索引
int index = -1, maxVal = Integer.MIN_VALUE;
for (int i = lo; i <= hi; i++) {
if (maxVal < nums[i]) {
index = i;
maxVal = nums[i];
}
}

TreeNode root = new TreeNode(maxVal);
// 递归调用构造左右子树
root.left = build(nums, lo, index - 1);
root.right = build(nums, index + 1, hi);

return root;
}

105. 从前序与中序遍历序列构造二叉树

此题依然沿用思路:使用分解子问题的方法解题:根节点 + 左子树 + 右子树

  • 对于根节点:利用序遍历的性质,中左右,开头第一个即为根节点
  • 对于左右子树:利用中序遍历的性质,左右中,我们可以找到root对应的index从而找到左右子树的范围
    • 对于左子树来说,序遍历的数组从 preStart + 1 开始 到 preStart + leftSize,中序遍历的数组则从inStart 开始 到 rootIdxInOrder - 1:
    • 对于右子树来说,序遍历的数组从 preStart + leftSize + 1 开始 到 preEnd,中序遍历的数组则从 rootIdxInOrder + 1 开始 到 inEnd:
  • ❗️总结:利用前序遍历找到root节点,利用中序遍历确定左右子树的区间

ps: 一点优化:用于快速查找root在inorder array里面的位置,由于tree里面的值保证不重复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Map<Integer, Integer> val2idxMap;
public TreeNode buildTree(int[] preorder, int[] inorder) {
// 继续使用分解子问题的方法解题:根节点 + 左子树 + 右子树
// 利用前序遍历的性质,中左右,开头第一个即为根节点
// 利用中序遍历的性质,左右中,我们可以找到root对应的index从而找到左右子树的范围

// 用于快速查找root在inorder array里面的位置,由于tree里面的值保证不重复:
val2idxMap = new HashMap<>();
for (int i = 0; i < inorder.length; i++) {
val2idxMap.put(inorder[i], i);
}

int preStart = 0, preEnd = preorder.length - 1;
int inStart = 0, inEnd = inorder.length - 1;
TreeNode root = build(preorder, preStart, preEnd, inorder, inStart, inEnd);
return root;
}

private TreeNode build(int[] preorder, int preStart, int preEnd,
int[] inorder, int inStart, int inEnd) {
if (preStart > preEnd) return null;
if (inStart > inEnd) return null;

int val = preorder[preStart];
TreeNode root = new TreeNode(val);
int rootIdxInOrder = val2idxMap.get(val);
int leftSize = rootIdxInOrder - inStart;

// 对于左子树来说,
// 前序遍历的数组从 preStart + 1 开始 到 preStart + leftSize
// 中序遍历的数组则从inStart 开始 到 rootIdxInOrder - 1:
root.left = build(preorder, preStart + 1, preStart + leftSize,
inorder, inStart, rootIdxInOrder - 1);

// 对于右子树来说,
// 前序遍历的数组从 preStart + leftSize + 1 开始 到 preEnd
// 中序遍历的数组则从 rootIdxInOrder + 1 开始 到 inEnd:
root.right = build(preorder, preStart + leftSize + 1, preEnd,
inorder, rootIdxInOrder + 1, inEnd);
return root;
}

106. 从中序与后序遍历序列构造二叉树

继续沿用上题思路:

使用分解子问题的方法解题:根节点 + 左子树 + 右子树

  • 对于根节点:利用序遍历的性质,左右中,右手边第一个即为根节点
  • 对于左右子树:利用中序遍历的性质,左右中,我们可以找到root对应的index从而找到左右子树的范围
    • PS: ⚠️为了测试,我这里使用rightTreeSize 但是使用leftTreeSize也是可以的:
    • 对于左子树来说,序遍历的数组从 postStart 开始 到 postEnd - rightTreeSize - 1,中序遍历的数组则从inStart 开始 到 rootIdx - 1:
    • 对于右子树来说,序遍历的数组从postEnd - rightTreeSize - 1 开始 到 postEnd - 1,中序遍历的数组则从 rootIdx + 1 开始 到 inEnd:
  • ❗️总结:利用序遍历找到root节点,利用中序遍历确定左右子树的区间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Map<Integer, Integer> val2IdxMap = new HashMap<>();
public TreeNode buildTree(int[] inorder, int[] postorder) {
// 继续 找根 + 左子树范围 + 右子树范围
// 用后序找根因为后序遍历的从后往前第一个是根节点
for (int i = 0; i < inorder.length; i++) {
val2IdxMap.put(inorder[i], i);
}

int postStart = 0, postEnd = postorder.length - 1;
int inStart = 0, inEnd = inorder.length - 1;
TreeNode root = build(postorder, postStart, postEnd, inorder, inStart, inEnd);
return root;
}

private TreeNode build(int[] postorder, int postStart, int postEnd,
int[] inorder, int inStart, int inEnd) {
if (postStart > postEnd) return null;
if (inStart > inEnd) return null;

int rootVal = postorder[postEnd];
int rootIdx = val2IdxMap.get(rootVal);
int rightTreeSize = inEnd - rootIdx;
TreeNode root = new TreeNode(rootVal);
root.left = build(postorder, postStart, postEnd - rightTreeSize - 1,
inorder, inStart, rootIdx - 1);
root.right = build(postorder, postEnd - rightTreeSize - 1, postEnd - 1,
inorder, rootIdx + 1, inEnd);
return root;
}

使用leftTreeSize的解法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
TreeNode build(int[] inorder, int inStart, int inEnd,
int[] postorder, int postStart, int postEnd) {

if (inStart > inEnd) {
return null;
}
// root 节点对应的值就是后序遍历数组的最后一个元素
int rootVal = postorder[postEnd];
// rootVal 在中序遍历数组中的索引
int index = valToIndex.get(rootVal);
// 左子树的节点个数
int leftSize = index - inStart;
TreeNode root = new TreeNode(rootVal);
// 递归构造左右子树
root.left = build(inorder, inStart, index - 1,
postorder, postStart, postStart + leftSize - 1);

root.right = build(inorder, index + 1, inEnd,
postorder, postStart + leftSize, postEnd - 1);
return root;
}

889. 根据前序和后序遍历构造二叉树

思路其实是相通的,但是:

⚠️通过前序中序,或者后序中序遍历结果可以确定唯一一棵原始二叉树,但是通过前序后序遍历结果无法确定唯一的原始二叉树。⚠️

思路:

  1. 首先把前序遍历结果的第一个元素或者后序遍历结果的最后一个元素确定为根节点的值。
  2. 然后把前序遍历结果的第二个元素作为左子树的根节点的值。
  3. 在后序遍历结果中寻找左子树根节点的值,从而确定了左子树的索引边界,进而确定右子树的索引边界,递归构造左右子树即可。

通过前序后序构造二叉树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Map<Integer, Integer> val2Idx = new HashMap<>();
public TreeNode constructFromPrePost(int[] preorder, int[] postorder) {
for (int i = 0; i < postorder.length; i++) {
val2Idx.put(postorder[i], i);
}
int preStart = 0, preEnd = preorder.length - 1,
postStart = 0, postEnd = postorder.length - 1;
TreeNode root = build(preorder, preStart, preEnd, postorder, postStart, postEnd);
return root;
}

private TreeNode build(int[] preorder, int preStart, int preEnd,
int[] postorder, int postStart, int postEnd) {
if (preStart > preEnd) return null;
if (postStart > postEnd) return null;
if (preEnd == preStart) return new TreeNode(preorder[preStart]);

int rootVal = preorder[preStart];
int leftRootVal = preorder[preStart + 1];

int leftTreeRootIdxInPostOrder = val2Idx.get(leftRootVal);
int leftTreeSize = leftTreeRootIdxInPostOrder - postStart + 1;

TreeNode root = new TreeNode(rootVal);
root.left = build(preorder, preStart + 1, preStart + leftTreeSize,
postorder, postStart, leftTreeRootIdxInPostOrder);
root.right = build(preorder, preStart + leftTreeSize + 1, preEnd,
postorder, leftTreeRootIdxInPostOrder + 1, postEnd - 1);
return root;
}

额外多了一个检查:

1
if (preEnd == preStart) return new TreeNode(preorder[preStart]);