解题思路
-
理解题目:题目要求我们计算给定
n个节点时可以构成的好二叉树的数量。好二叉树的定义是每个节点要么没有孩子,要么有两个孩子。这意味着树的节点数n必须是奇数。 -
动态规划:我们可以使用动态规划来解决这个问题。定义
dp[i]为有i个节点时可以构成的好二叉树的数量。 -
状态转移方程:对于每个节点数
i,我们可以将其分解为根节点和左右子树。假设左子树有j个节点,那么右子树就有i - 1 - j个节点(因为根节点占用一个节点)。因此,状态转移方程为:plaintext
dp[i] = sum(dp[j] * dp
[i - 1 - j]) for j in
range(1, i, 2)
这里
j和i - 1 - j都是奇数,因为它们必须是好二叉树的节点数。 -
初始条件:
dp[1] = 1,因为只有一个节点时,只有一种好二叉树(即只有一个根节点)。 -
-
- 取模:由于结果需要对
10^9 + 7取模,我们在每次计算时都需要取模。
- 取模:由于结果需要对
-
public class Main {
public static int solution(int n) {
// 如果n是偶数,直接返回0,因为不可能构成好二叉树
if (n % 2 == 0) return 0;
// 定义模数
final int MOD = 1000000007;
// 初始化dp数组
int[] dp = new int[n + 1];
dp[1] = 1; // 初始条件
// 填充dp数组
for (int i = 3; i <= n; i += 2) {
for (int j = 1; j < i; j += 2) {
dp[i] = (dp[i] + (dp[j] * dp[i - 1 - j]) % MOD) % MOD;
}
}
return dp[n];
}
public static void main(String[] args) {
System.out.println(solution(5) == 2);
System.out.println(solution(7) == 5);
System.out.println(solution(9) == 14);
}
}
代码解释
-
初始检查:
java
if (n % 2 == 0)
return 0;
这一行代码检查
n是否为偶数。如果是偶数,直接返回0,因为根据题目描述,好二叉树的节点数必须是奇数。 -
定义模数:
java
final int MOD =
1000000007;
这一行定义了一个常量
MOD,用于在计算过程中对结果取模,以防止整数溢出。 -
初始化
dp数组:java
int[] dp = new int[n
+ 1];
dp[1] = 1; // 初始条件
这里初始化了一个大小为
n + 1的数组dp,用于存储动态规划的结果。dp[1]被初始化为1,因为只有一个节点时,只有一种好二叉树(即只有一个根节点)。 -
填充
dp数组:java
for (int i = 3; i <=
n; i += 2) {
for (int j = 1; j
< i; j += 2) {
dp[i] = (dp
[i] + (dp[j]
* dp[i - 1 -
j]) % MOD) %
MOD;
}
}
这一部分是核心的动态规划逻辑。外层循环遍历所有奇数
i,表示当前考虑的节点数。内层循环遍历所有可能的左子树节点数j,并计算对应的右子树节点数i - 1 - j。然后,根据状态转移方程dp[i] = sum(dp[j] * dp[i - 1 - j])更新dp[i],并在每次计算时对结果取模。 -
返回结果:
java
return dp[n];
最后,返回
dp[n],即有n个节点时可以构成的好二叉树的数量。
关键点
- 动态规划:通过
dp数组存储中间结果,避免重复计算。 - 状态转移方程:
dp[i] = sum(dp[j] * dp[i - 1 - j]),表示将i个节点分解为根节点和左右子树的组合。 - 取模操作:在每次计算时对结果取模,防止整数溢出。
通过这些步骤,代码能够正确计算出给定 n 个节点时可以构成的好二叉树的数量。