构造类问题思路:
labuladong 的总结很好
二叉树的构造问题一般都是使用「分解问题」 的思路:构造整棵树 = 根节点 + 构造左子树 + 构造右子树。
经典的构造题有三道:
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]); 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 ); } TreeNode build (int [] nums, int lo, int hi) { 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; }
此题依然沿用思路:使用分解子问题的方法解题:根节点 + 左子树 + 右子树
对于根节点:利用前 序遍历的性质,中左右,开头第一个即为根节点
对于左右子树:利用中序遍历的性质,左右中,我们可以找到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) { 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; root.left = build(preorder, preStart + 1 , preStart + leftSize, inorder, inStart, rootIdxInOrder - 1 ); root.right = build(preorder, preStart + leftSize + 1 , preEnd, inorder, rootIdxInOrder + 1 , inEnd); return root; }
继续沿用上题思路:
使用分解子问题的方法解题:根节点 + 左子树 + 右子树
对于根节点:利用后 序遍历的性质,左右中,右手边第一个即为根节点
对于左右子树:利用中序遍历的性质,左右中,我们可以找到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 ; } int rootVal = postorder[postEnd]; 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; }
思路其实是相通的,但是:
⚠️通过前序中序,或者后序中序遍历结果可以确定唯一一棵原始二叉树,但是通过前序后序遍历结果无法确定唯一的原始二叉树 。⚠️
思路:
首先把前序遍历结果的第一个元素或者后序遍历结果的最后一个元素确定为根节点的值。
然后把前序遍历结果的第二个元素作为左子树的根节点的值。
在后序遍历结果中寻找左子树根节点的值,从而确定了左子树的索引边界,进而确定右子树的索引边界,递归构造左右子树即可。
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]);