[路飞]_前端算法第九十七弹-不同的二叉搜索树 II

121 阅读3分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战

给你一个整数 n ,请你生成并返回所有由 n 个节点组成且节点值从 1n 互不相同的不同 二叉搜索树 **。可以按 任意顺序 返回答案。

示例 1:

图片.png

输入:n = 3
输出:[[1,null,2,null,3],[1,null,3,2],[2,1,3],[3,1,null,null,2],[3,2,null,1]]

示例 2:

输入:n = 1
输出:[[1]]

这道题和之前的一道不同的二叉搜索树查不多了,只是之前只需要输出种类数,本题需要输出二叉树。

回顾下不同的二叉搜索树那道题中的逻辑:

  • 使用指针 i 将数字切分左右分段
  • dp[i]存放指针在 i 时存在的所有可能二叉树数量
  • 左右二叉树种类数相乘

那本题的逻辑似乎也就清晰:

  • 对数字分段的逻辑可以沿用
  • dp 就不能只存放数量了,需要存放二叉树(其实这个逻辑还是好实现的[TreeNode()])
  • 遍历 i 左右的二叉树时就会发现,不仅要多左侧已经生成的二叉树集合做增加节点的操作,还要对右侧做删除节点的操作
  • 统计数量,可以通过公式推导,但返回真实二叉树就需要枚举所有可能

要计算增加一个节点而枚举所有的节点,可以尝试直接使用下面逻辑推导:

  • 先任取一个元素生成 TreeNode,然后再向这个 TreeNode 不断增加节点
  • 返回节点数累加到 n 时所有的可能 TreeNode

追加节点到已存在树,那剩下的问题就是:

  • 要怎么存放哪些已经存在的树
  • 怎么在原有树的基础上枚举新加入节点带来的二叉树种类

存放的问题,可以直接推送到要返回的结果数组里面存贮,那么在推送时,就需要是全节点的树;

综合上面的逻辑,用 i 分割了左侧 left,和右侧 right,那这个全节点的树就应该是: treeLeft - TreeNode(i) - treeRight (其中 treeLeft、treeRight 均为在指定范围生成新二叉树,不存在追加节点和删除节点的问题)

如果这个范围是 1 到 n,这时逻辑回归了题目的逻辑,递归。

var generateTrees = function (n) {
  function buildTree(start, end) {
    let _result = []
    // 指针交错递归终止
    if (start > end) return [null]
    // i指针滑动,枚举left和right分段的所有可能
    for (let i = start; i <= end; i++) {
      // 左侧和右侧生成树的集合 返回为数组
      let left = buildTree(start, i - 1)
      let right = buildTree(i + 1, end)
      // 循环左右两侧的树集合 分别拼接到新树上,并且存储到结果数组中
      for (const leftNode of left) {
        for (const rightNode of right) {
          let node = new TreeNode(i)
          node.left = leftNode
          node.right = rightNode
          _result.push(node)
        }
      }
    }
    // 返回指定范围生成的树集合
    return _result
  }
  // n 为 0 是返回[]
  if (n === 0) return []
  // 指定最大范围
  return buildTree(1, n)
}