前言
二叉树解题的思维模式分两类:
1、是否可以通过遍历一遍二叉树得到答案?
如果可以,用一个 traverse 函数配合外部变量来实现,这叫「遍历」的思维模式。
2、是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案?
如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值,这叫「分解问题」的思维模式。
无论使用哪种思维模式,你都需要思考:如果单独抽出一个二叉树节点,它需要做什么事情?需要在什么时候(前/中/后序位置)做?其他的节点不用你操心,递归函数会帮你在所有节点上执行相同的操作。
二叉树的构造问题一般都是使用「分解问题」的思路:构造整棵树 = 根节点 + 构造左子树 + 构造右子树。
题解
解法一:基于分解子问题思维模式的递归
首先,肯定要想办法确定根节点的值,把根节点做出来,然后递归构造左右子树即可
前序遍历的第一个值 preorder[0] 就是根节点的值。
找到中序遍历数组中的根节点位置,左右两个子数组即为左右子树的中序遍历结果
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func buildTree(preorder []int, inorder []int) *TreeNode {
return build(preorder, 0, len(preorder)-1, inorder, 0, len(inorder)-1)
}
// build 函数定义:
// 若前序遍历数组为 preorder[preStart..preEnd],
// 中序遍历数组为 inorder[inStart..inEnd],
// 构造二叉树,返回该二叉树的根节点
func build(preorder []int, preStart, preEnd int, inorder []int, inStart, inEnd int) *TreeNode{
if preStart > preEnd{ // base case
return nil
}
rootVal := preorder[preStart] // 前序遍历数组第一个就是根节点
rootInorderIndex := 0 // 寻找root在中序遍历数组的位置
for idx, v := range inorder{
if v == rootVal{
rootInorderIndex = idx
break
}
}
root := &TreeNode{Val: rootVal}
leftSize := rootInorderIndex - inStart // 左子树的节点数
// 递归构造左右子树
root.Left = build(preorder, preStart+1, preStart+leftSize, inorder, inStart, rootInorderIndex-1)
root.Right = build(preorder, preStart+leftSize+1, preEnd, inorder, rootInorderIndex+1, inEnd)
return root
}
- 时间复杂度:O(n^2),每次递归调用都需要在中序数组中搜索根节点位置,对于每个节点,都会递归执行一遍
- 空间复杂度:O(n),递归栈深度
解法二: 空间换时间优化
通过 for 循环遍历的方式去确定 rootInorderIndex 效率不算高,可以进一步优化,因为题目说二叉树节点的值不存在重复,所以可以使用一个 HashMap 存储元素到索引的映射,这样就可以直接通过 HashMap 查到 rootVal 对应的 rootInorderIndex。
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func buildTree(preorder []int, inorder []int) *TreeNode {
inorderMap := make(map[int]int) // {val: index}
for idx, v := range inorder{
inorderMap[v] = idx
}
return build(preorder, 0, len(preorder)-1, inorder, 0, len(inorder)-1, inorderMap)
}
func build(preorder []int, preStart, preEnd int, inorder []int, inStart, inEnd int, inorderMap map[int]int) *TreeNode{
if preStart > preEnd{ // base case
return nil
}
rootVal := preorder[preStart] // 前序遍历数组第一个就是根节点
rootInorderIndex := inorderMap[rootVal] // 寻找root在中序遍历数组的位置
root := &TreeNode{Val: rootVal}
leftSize := rootInorderIndex - inStart // 左子树的节点数
// 递归构造左右子树
root.Left = build(preorder, preStart+1, preStart+leftSize, inorder, inStart, rootInorderIndex-1, inorderMap)
root.Right = build(preorder, preStart+leftSize+1, preEnd, inorder, rootInorderIndex+1, inEnd, inorderMap)
return root
}
- 时间复杂度:O(n),利用哈希将每次递归调用中搜索rootInorderIndex的时间从 O(n) 降至 O(1),只需要考虑递归层级
- 空间复杂度:O(n),递归栈 + 哈希表