这道题目在 《剑指Offer》一书中有提到过,也有解析的过程,不过时间隔得久,大概只有潜意识的印象了。
初看这道题有点让人摸不着头脑,虽然一直强调树的算法一般是递归的算法,就算是递归算法,也无从下手
那就先举个例子先画几个图看看情况, 以这棵树为例
我们先把前序遍历和中序遍历列在一起, 然后将对应关系用箭头连接起来
首先是 红色的箭头, 前序遍历的第0个元素对应中序遍历的第1个元素
然后我们来观察观察这个3和这个树的关系, 中序遍历给我们直观的感受, 就是将树分成了左右两棵子树
上图中左右两个部分正好就是左子树和右子树, 是不是有点头绪了?
接下来我们再来分别考察左右两个子树
左子树:
我们已经知道 9 属于 3 的左子树, 而 9 刚好是 第0个元素, 它的右边就是他的父节点3, 所以只剩下孤零零的 9, 也就是说, 9 是 3 的左叶子
右子树:
前面我们知道 15 20 7 组成了 3 的右子树, 那它们又是怎么排列的呢?
3,9 我们用过了, 现在我们来看20, 20 刚好位于15 和 7 的中间, 再来看看我们的树, 20正好是 15 和 7 的父节点, 这个不可能又是巧合吧?
如果我们把一开始的例子换成是这个, 有没有发现同样的分析过程也适用.
说到底还是个递归问题呀
这种方式的缺点是, 递归太多次了, 每次一个元素不在对应的子树范围中,就尝试下一个元素, 这样会比较浪费
针对这种情况,我们可以改一下内部的递归函数,同时限制先序遍历的范围
这样可以减少遍历的次数,不过计算的边界的时候要小心, 最好多举一些例子去研究他们的关系
实际上这里 preTo 没什么作用, 应该可以直接去掉
其实递归的题目做多了就会有一种感觉, 要培养自己递归的思维方式.
/**
* inFrom < inTo 会出现在 [4,1,2,3], [1,2,3,4] 这个用例里面
* 因为有 index - 1 这种操作
*/
def buildTree(preorder: Array[Int], inorder: Array[Int]): TreeNode = {
val len = preorder.length
def buildByInorder(preIndex: Int, inFrom: Int, inTo: Int): TreeNode = {
if (preIndex >= len || inFrom > inTo) null //只要是有减法, 就有这种边界的情况要注意
else if (inFrom == inTo) new TreeNode(inorder(inFrom))
else {
val preorderValue = preorder(preIndex) //根据当前的下标, 用先序遍历的值, 去查询中序遍历的位置
inorder.indexOf(preorderValue, inFrom) match {
case index if index < inFrom || index > inTo => //index 超过了子树的范围, 表示当前值在另一个子树中
buildByInorder(preIndex + 1, inFrom, inTo) //使用下一个元素构建尝试匹配相同的范围
case index =>
val node = new TreeNode(inorder(index))
//左子树的元素是 中序遍历 的 从 inFrom 到 index - 1 组成的元素
val left = buildByInorder(preIndex + 1, inFrom, index - 1)
//右子树的元素是 中序遍历 的 从 index + 1 到 inTo 组成的元素
val right = buildByInorder(preIndex + 1, index + 1, inTo)
node.left = left
node.right = right
node
}
}
}
if (preorder.length == inorder.length && preorder.nonEmpty) {
buildByInorder(0, 0, len - 1)
} else null
}
def buildTree1(preorder: Array[Int], inorder: Array[Int]): TreeNode = {
val len = preorder.length
def buildByInorder1(preFrom: Int, preTo: Int, inFrom: Int, inTo: Int): TreeNode = {
if (inFrom >= len || preFrom >= len || preTo < preFrom) {
null
} else if (inFrom == inTo) {
new TreeNode(inorder(inFrom))
} else {
val preHead = preorder(preFrom)
inorder.indexOf(preHead, inFrom) - inFrom match {//注意这里要减去 inFrom, 表示是范围内的第几个元素
case -1 => null //如果不在范围内 或者 超出范围, 则返回 null
case index if index > inTo => null
case index =>
val node = new TreeNode(preHead)
val leftInFrom = inFrom
val leftInTo = inFrom + index - 1
val rightInFrom = inFrom + index + 1
val rightInTo = inTo
val left = buildByInorder1(preFrom + 1, preFrom + index, leftInFrom, leftInTo) //分别构建左子树
val right = buildByInorder1(preFrom + index + 1, preTo, rightInFrom, rightInTo) //分别构建右子树
node.left = left
node.right = right
node
}
}
}
if (preorder.length == inorder.length && preorder.nonEmpty) buildByInorder1(0, len - 1, 0, len - 1)
else null
}