面试经典 106. 从中序与后序遍历序列构造二叉树

80 阅读4分钟
  • 最近小胖开始找工作了,又来刷苦逼的算法了 555
  • 废话不多说,看这一题,上链接:leetcode.cn/problems/co…

截屏2024-07-08 10.29.31.png

  • 看到这一题有两个很重要的基础知识点需要知道
    • 什么是二叉树?
    • 什么是中序遍历和后序遍历?
  • 先回答第一个问题,什么是二叉树?这是一个数据结构,是一种常见的树状数据结构,其中每个节点最多有两个子节点,分别称为左子节点和右子节点,如上图。
  • 了解什么是二叉树,才能回答第二问题,什么是中序遍历和后序遍历?首先这两个都是一种遍历整个二叉树的方法,只不过遍历节点的顺序不同产生不同的名字
    • 中序遍历:按照左子树 -> 根节点 -> 右子树的顺序遍历二叉树
    • 后序遍历:按照左子树 -> 右子树 -> 根节点的顺序遍历二叉树
  • 好了,了解了这两个基础知识,现在就来解决这道题。这道题的核心在于你拥有两个顺序的方式,如何构建一棵树呢?
  • 从上面例子入手,后序遍历是 9,15,7,20,3,我们知道后序遍历的顺序是 左,右,根,所以左右一个元素肯定是根节点,这棵树的根节点就是 3 。
  • 得到了这个信息有什么用呢?用处大了,我们可以拿这个 3 去中序遍历找 左子树的范围 和 右子树的范围,如下图:

截屏2024-07-08 10.47.32.png

  • 这样子我们是不是就把左子树和右子树的范围找出来了呢,所以这个过程的逻辑就是这样的:
    • 先获取后序遍历数组的最后一个元素,得到根节点
    • 再根据根节点 去 中序遍历数组中寻找对应的位置
    • 找到位置 i 后,开始圈定范围
    • 左子树 元素范围是 0 - i
    • 右子树 元素范围是 i+1 - n(数组的最后一个元素)
  • 找到范围了,然后咋办呢?
  • 这个时候就需要一个很牛逼的思想,分治的思想
  • 什么是分治?分治就是一种问题解决策略,它将一个大问题划分为多个相同或类似的子问题,然后递归地解决这些子问题,最后将子问题的解合并起来得到大问题的解。
  • 所以这里我们可以将找出的 左子树 当作一个新的树,继续上述的操作,直到只剩一个节点为止,就如上述的 9 节点。右子树同理
  • 这样我们的二叉树就可以完成了,具体代码如下:
func dfs106(inorder []int, postorder []int) *TreeNode {
  //当中序数组为空,则没有元素,返回nil
  if len(inorder) == 0 {
    return nil
  }
  //根节点为后序数组的最后一个元素
  head := &TreeNode{Val: postorder[len(postorder)-1]}
  //在中序数组中找到根节点的位置
  i := 0
  for ; i < len(inorder); i++ {
    if inorder[i] == postorder[len(postorder)-1] {
      break
    }
  }
  //递归构建 左子树
  head.Left = dfs106(inorder[:i], postorder[:i])
  //递归构建 右子树
  head.Right = dfs106(inorder[i+1:], postorder[i:len(postorder)-1])
  //返回根节点
  return head
}
  • 这里可以有个小优化,因为每次递归都需要遍历 中序数组,太过耗时了,本着空间换时间的策略,用一个 map 存储每个中序数组元素的索引,这样查找 左右子树的范围的时候就会快很多,代码如下:
func Solution106v2(inorder []int, postorder []int) *TreeNode {
  
  // 获取中序遍历的索引
  n := len(inorder)
  index := make(map[int]int, n)
  for i, v := range inorder {
    index[v] = i
  }
​
  var build func(int, int, int, int) *TreeNode
  build = func(p1, p2, i1, i2 int) *TreeNode {
    // 递归终止条件
    if p1 > p2 {
      return nil
    }
    // 根节点
    root := &TreeNode{Val: postorder[p2]}
    // 中序遍历中根节点的位置
    i := index[postorder[p2]]
    // 左子树的节点个数
    leftSize := i - i1
    // 递归构建左右子树
    root.Left = build(p1, p1+leftSize-1, i1, i-1)
    root.Right = build(p1+leftSize, p2-1, i+1, i2)
    return root
  }
​
  return build(0, n-1, 0, n-1)
​
}
  • 到这里就结束了,这道题写了我足足一个上午,看来还是太菜了需要多练,不说了接着做题了