leetcode
leetcode 801 ~ 850
所有可能的真二叉树

所有可能的真二叉树

难度:

标签:

题目描述

给你一个整数 n ,请你找出所有可能含 n 个节点的 真二叉树 ,并以列表形式返回。答案中每棵树的每个节点都必须符合 Node.val == 0

答案的每个元素都是一棵真二叉树的根节点。你可以按 任意顺序 返回最终的真二叉树列表

真二叉树 是一类二叉树,树中每个节点恰好有 02 个子节点。

 

示例 1:

输入:n = 7
输出:[[0,0,0,null,null,0,0,null,null,0,0],[0,0,0,null,null,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,null,null,null,null,0,0],[0,0,0,0,0,null,null,0,0]]

示例 2:

输入:n = 3
输出:[[0,0,0]]

 

提示:

  • 1 <= n <= 20

代码结果

运行时间: 50 ms, 内存: 19.6 MB


/*
 * 思路:
 * - 同样是递归生成真二叉树,只是使用Java Stream的方式来实现。
 * - 通过flatMap将递归结果展平,并使用collect进行结果的收集。
 */

import java.util.*;
import java.util.stream.*;

class TreeNode {
    int val;
    TreeNode left, right;
    TreeNode(int val) { this.val = val; }
}

public class Solution {
    public List<TreeNode> allPossibleFBT(int n) {
        if (n % 2 == 0) return new ArrayList<>();
        if (n == 1) return Collections.singletonList(new TreeNode(0));
        n--;
        return IntStream.range(1, n)
                        .filter(i -> i % 2 == 1)
                        .mapToObj(i -> allPossibleFBT(i)
                                      .stream()
                                      .flatMap(left -> allPossibleFBT(n - i)
                                                     .stream()
                                                     .map(right -> {
                                                         TreeNode root = new TreeNode(0);
                                                         root.left = left;
                                                         root.right = right;
                                                         return root;
                                                     })))
                        .flatMap(x -> x)
                        .collect(Collectors.toList());
    }
}

解释

方法:

这个题解使用了动态规划的思想。首先,它初始化了一个列表 `f`,其中 `f[i]` 用于存储所有包含 `i` 个节点的真二叉树的根节点。由于真二叉树的节点数必须是奇数(除了根节点外,每两个子节点加一个节点),因此只需要计算奇数个节点的情形。对于每个奇数 `i`,遍历所有可能的左子树大小 `left_size`(也必须是奇数),然后对于每个 `left_size`,通过 `product` 函数从 `f[left_size]` 和 `f[i - 1 - left_size]` 中取出所有可能的左、右子树组合,为每对左右子树创建一个新的根节点,最后将这些树根节点存储在 `f[i]` 中。`product` 是笛卡尔积,用于生成所有可能的左右子树组合。

时间复杂度:

O(4^n / n^(3/2))

空间复杂度:

O(4^n / n^(3/2))

代码细节讲解

🦆
在你的题解中,你提到了使用动态规划的方法,能否详细解释为什么选择动态规划而不是其他算法?
在解决'所有可能的真二叉树'问题时,动态规划是一个合适的选择,因为这个问题具有明显的最优子结构特性。每棵真二叉树都可以通过选择左右子树的不同组合来构建,而这些子树自身也是真二叉树。动态规划允许我们存储和重用较小子问题的解(即较小数量节点的所有可能真二叉树形态),从而避免了重复计算,并大幅提高效率。如果使用纯递归或其他方法,每次需要构建同样大小的真二叉树时都需重新计算,效率会明显降低。
🦆
你提到了`product`函数用于生成所有可能的左右子树组合,这是否意味着每次递归调用都会重新计算这些组合,还是说有缓存机制来避免重复计算?
在此题解中,使用`product`函数确实是为了生成所有可能的左右子树组合,但这并不意味着每次都会重新计算所有子树组合。动态规划的实现方式通过缓存机制存储了每个节点数下所有可能的真二叉树(在列表`f`中)。因此,当使用`product`函数组合左右子树时,它实际上是从已经计算并存储的结果中提取组合,而不是重新计算每棵子树。这极大地减少了计算量并提高了效率。
🦆
在题解中,`f[i]`的初始化为什么从i=3开始,并且为什么只考虑奇数个节点?n=2时是否不可能构成真二叉树,如果是这样,请解释原因。
在题解中,`f[i]`的初始化从i=3开始,并且只考虑奇数个节点,是因为根据真二叉树的定义,除了根节点外,每个节点都必须有两个子节点。这意味着任何真二叉树的节点数必须是奇数。例如,一个节点的树是真二叉树(只有根),三个节点的树可以是根节点下挂两个子节点,但两个节点的树不能满足每个非根节点都有两个子节点的条件,因此不是真二叉树。
🦆
题解中使用了递归的方法,递归深度是否会成为处理大规模数据时的一个限制因素?如果是,有什么可能的解决方案?
尽管题解中使用了递归的方法,但由于我们利用了动态规划存储了中间结果,实际上降低了递归的需求,因为大部分时间我们是在读取预先计算的结果而非发起新的递归调用。这极大地减少了递归深度。对于极大规模数据,如果递归深度仍然是问题,可以考虑使用迭代的方式重写算法,或者在语言支持的情况下增加递归调用栈大小。

相关问题