题目四:
解法一:(递归)(根据示例输入画图比划)
解题思路:以后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
- 第一步:如果数组大小为零的话,说明是空节点了。
- 第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
- 第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
- 第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
- 第五步:切割后序数组,切成后序左数组和后序右数组
- 第六步:递归处理左区间和右区间
如何切割,以及边界值找不好很容易乱套。
此时应该注意确定切割的标准,是左闭右开,还有左开右闭,还是左闭右闭,这个就是不变量,要在递归中保持这个不变量。
在切割的过程中会产生四个区间,把握不好不变量的话,一会左闭右开,一会左闭右闭,必然乱套!
首先要切割中序数组,为什么先切割中序数组呢?
切割点在后序数组的最后一个元素,就是用这个元素来切割中序数组的,所以必要先切割中序数组。
中序数组相对比较好切,找到切割点(后序数组的最后一个元素)在中序数组的位置,然后切割。
接下来就要切割后序数组了。
首先后序数组的最后一个元素指定不能要了,这是切割点 也是 当前二叉树中间节点的元素,已经用了。
后序数组的切割点怎么找?
后序数组没有明确的切割元素来进行左右切割,不像中序数组有明确的切割点,切割点左右分开就可以了。
此时有一个很重的点,就是中序数组大小一定是和后序数组的大小相同的(这是必然)。
中序数组我们都切成了左中序数组和右中序数组了,那么后序数组就可以按照左中序数组的大小来切割,切成左后序数组和右后序数组。
此时,中序数组切成了左中序数组和右中序数组,后序数组切割成左后序数组和右后序数组。接下来可以递归了。
完整代码:
var buildTree = function(inorder, postorder) {
if (!inorder.length) return null;
const rootVal = postorder.pop(); // 从后序遍历的数组中获取中间节点的值, 即数组最后一个值
let rootIndex = inorder.indexOf(rootVal); // 获取中间节点在中序遍历中的下标
const root = new TreeNode(rootVal); // 创建中间节点
root.left = buildTree(inorder.slice(0, rootIndex), postorder.slice(0, rootIndex)); // 创建左节点
root.right = buildTree(inorder.slice(rootIndex + 1), postorder.slice(rootIndex)); // 创建右节点
return root;
};
前序和中序可以唯一确定一棵二叉树。
后序和中序可以唯一确定一棵二叉树。
那么前序和后序可不可以唯一确定一棵二叉树呢?
前序和后序不能唯一确定一棵二叉树! ,因为没有中序遍历无法确定左右部分,也就是无法分割。
举一个例子:
前序和中序可以唯一确定一棵二叉树。
后序和中序可以唯一确定一棵二叉树。
那么前序和后序可不可以唯一确定一棵二叉树呢?
前序和后序不能唯一确定一棵二叉树! ,因为没有中序遍历无法确定左右部分,也就是无法分割。
举一个例子:
tree1 的前序遍历是[1 2 3], 后序遍历是[3 2 1]。
tree2 的前序遍历是[1 2 3], 后序遍历是[3 2 1]。
那么tree1 和 tree2 的前序和后序完全相同,这是一棵树么,很明显是两棵树!
所以前序和后序不能唯一确定一棵二叉树!
解法二:参考他人
有了左右子树的 postOrder 和 inorder 之后,就能继续递归构建左、右子树,一直递归下去,直到:无法形成 postOrder 和 inorder 数组,就构建不出子树了,即来到树的底部了,返回 null 节点。
递归函数可以选择接受数组本身,也可以接收指针,我采用后者,根据指针 iStart 到指针 iEnd 的 inorder 数组,和从 pStart 到 pEnd 的 postorder 数组,构建当前子树,避免每次递归都要切割字符串。
定位root在inorder数组中的位置 我用了一个 map 去提前存下所有节点值在 inorder 数组中的索引,这样就不用每次都花 O(n) 的时间去定位 root 的位置。(不用类似 indexOf 这样的库函数),用空间换取时间。
const buildTree = (inorder, postorder) => {
const map = {};
for (let i = 0; i < inorder.length; i++) { // 将节点值在inorder数组中的位置提前存入map
map[inorder[i]] = i;
}
// 根据iStart到iEnd的inorder数组,和从pStart到pEnd的postorder数组构建当前子树
const helper = (iStart, iEnd, pStart, pEnd) => {
if (pStart > pEnd || iStart > iEnd) { // 指针交错了,返回null节点
return null;
}
const rootVal = postorder[pEnd]; // 获取当前要构建的根节点的值
const mid = map[rootVal]; // 获取到它在inorder数组中的位置
const leftNodeNum = mid - iStart; // 获取左子树的节点个数
const root = new TreeNode(rootVal); // 创建节点
root.left = helper(iStart, mid - 1, pStart, pStart + leftNodeNum - 1); // 用递归构建左子树
root.right = helper(mid + 1, iEnd, pStart + leftNodeNum, pEnd - 1); // 用递归构建右子树
return root; // 返回当前构建好的子树
};
return helper(0, inorder.length - 1, 0, postorder.length - 1); // 递归的入口
};