关键字:树,递归算法
归航return:(Trivial) LeetCode 289—生命游戏zhuanlan.zhihu.comProblem
Given preorder and inorder traversal of a tree, construct the binary tree.
Note: You may assume that duplicates do not exist in the tree.
For example, given
preorder = [3,9,20,15,7]
inorder = [9,3,15,20,7]
Return the following binary tree:
3
/
9 20
/
15 7
Solution
这道题目是一道很基础的二叉树问题,要求从前序遍历和中序遍历中,复原一个二叉树。要解决这个问题,首先要回归到这两种遍历方法的定义上来。
前序遍历:如果当前节点不为空:那么:(1)访问当前节点(2)递归地访问当前节点的左子树(3)递归地访问当前节点的右子树;
中序遍历:如果当前节点不为空,那么:(1)递归地访问当前节点的左子树(2)访问当前节点(3)递归地访问当前节点的右子树。
因此,我们可以将访问得到的结果当成三个部分,对于前序遍历来说,就是:[current, left, right]
,对于中序遍历来说,就是 [left, current, right]
,而 current 因为只有一个元素而且是前序遍历范围中的开头元素,因此是相对好确定的位置。因为题目假设 duplicates do not exist in the tree,因此可以使用哈希表的方法,预先存储中序遍历结果的元素和下标的位置关系,这样就可以很容易地从 current,也就是前序遍历的开头,快速地找到分界点的位置。又因为我们这里需要存储 left
和 right
在数组中下标的位置,因此还需要递归函数中包含四个额外变量,分别对应这两个区间的范围。按照我的习惯,我习惯使用左闭右开区间,因为这样可以让代码更加统一,更难出错。C++ 的 STL 中,大量的算法,定义都是使用的左开右闭区间。我相信,没有人比标准库的开发者更懂这些基础算法的优化细节。
代码如下:
class Solution {
HashMap<Integer, Integer> inorderHashMap;
public TreeNode buildTree(int[] preorder, int[] inorder) {
if (preorder.length == 0){
return null;
}
inorderHashMap = new HashMap<>();
for (int i = 0; i < inorder.length; ++i){
inorderHashMap.put(inorder[i], i);
}
return buildTreeHelper(preorder, 0, preorder.length, 0, inorder.length);
}
private TreeNode buildTreeHelper(int[] preorder, int preorderLeft, int preorderRight, int inorderLeft, int inorderRight){
if (preorderLeft == preorderRight){
return null;
}
TreeNode ret = new TreeNode(preorder[preorderLeft]);
int idx = inorderHashMap.get(ret.val);
ret.left = buildTreeHelper(preorder, preorderLeft+1, preorderLeft+1+(idx-inorderLeft), inorderLeft, idx);
ret.right = buildTreeHelper(preorder, preorderRight-(inorderRight-idx-1), preorderRight, idx+1, inorderRight);
return ret;
}
}
这里递归参数虽然有四个,但是因为子树无论是如何遍历,节点的个数是相等的,因此在我们的递归调用中 preorderRight-preorderLeft == inorderRight-inorderLeft
,这样可以减少思维量。
涉及到递归的算法通常比较难估计,不过考虑极端情况,就是二叉树退化成链表,这个时候的时间复杂度是 O(N)
,空间复杂度,包括栈调用的复杂度,就是 O(N)
,其中 N
是二叉树中,节点的个数。
EOF。