LeetCode 823. 带因子的二叉树
题目描述

题意分析
数组中的数互不相同且都大于 1,每个数可以被重复用作任意多个节点。构造二叉树时,每个非叶节点的值必须等于左右孩子值的乘积,求不同二叉树的总数,对
10^9 + 7取模;左右子树的位置需要区分。
解法:排序 + 按根值动态规划
核心思路
[!blue]
按根节点值分类,令
dp[i]表示以arr[i]为根的树的数量。每个值都能单独形成叶子节点,所以状态初值为 1;非叶树再按左右孩子的根值分类累加。先将数组升序排序。由于两个因子都大于 1,任何合法孩子值都严格小于根值,因此计算
dp[i]时,左右子树所需状态已经算好。枚举j < i作为左孩子,只有arr[j]整除根值时才继续,并用哈希表查找右孩子值arr[i] / arr[j]是否存在。若右孩子对应下标
k,左子树有dp[j]种选择,右子树有dp[k]种选择,两侧独立,因此贡献dp[j] * dp[k]。每棵非叶树都有唯一的左右根值和左右子树组合,所以这些分类既不漏算,也不重复。左右根值不同时,枚举左因子会分别遇到两个方向,已经计入镜像次序;两值相同时只枚举一次,但左右子树形状仍可独立选择,平方本身已覆盖所有有序组合,不能再额外乘二。数组值可重复使用,因此不需要从可用集合中移除孩子值。
每次贡献相乘后累加并取模,乘法使用宽整数,避免在取模之前溢出。最终根节点可以取数组中任意值,所以答案是所有
dp[i]的和,而不是最后一个状态。
解题步骤
- 排序并建立数值到下标的映射。
- 所有根状态初始化为一。
- 逐根枚举小于当前根值的左因子,右因子存在则累加子树方案数乘积。
- 每次累加取模,最终求全部根状态之和。
代码实现
class Solution {
public int numFactoredBinaryTrees(int[] arr) {
long mod = 1_000_000_007L;
Arrays.sort(arr);
Map<Integer, Integer> indexMap = new HashMap<>();
for (int i = 0; i < arr.length; i++) {
indexMap.put(arr[i], i);
}
long[] dp = new long[arr.length];
// 每个值单独作叶子也是一棵树,基础方案数为一。
Arrays.fill(dp, 1);
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < i; j++) {
if (arr[i] % arr[j] != 0) {
continue;
}
int right = arr[i] / arr[j];
Integer k = indexMap.get(right);
if (k != null) {
// 左右子树选择独立,方案数相乘;枚举左因子自动区分左右次序。
dp[i] = (dp[i] + dp[j] * dp[k]) % mod;
}
}
}
long answer = 0;
for (long v : dp) {
answer = (answer + v) % mod;
}
return (int) answer;
}
}
import "sort"
func numFactoredBinaryTrees(arr []int) int {
const MOD int64 = 1_000_000_007
sort.Ints(arr)
n := len(arr)
indexMap := make(map[int]int, n)
for i, v := range arr {
indexMap[v] = i
}
dp := make([]int64, n)
// 每个值单独作叶子也是一棵树,基础方案数为一。
for i := range dp {
dp[i] = 1
}
for i := 0; i < n; i++ {
for j := 0; j < i; j++ {
if arr[i]%arr[j] == 0 {
right := arr[i] / arr[j]
if k, ok := indexMap[right]; ok {
// 左右子树选择独立,方案数相乘;枚举左因子自动区分左右次序。
dp[i] = (dp[i] + dp[j]*dp[k]) % MOD
}
}
}
}
var answer int64
for _, v := range dp {
answer = (answer + v) % MOD
}
return int(answer)
}
复杂度分析
- 时间复杂度:哈希查询按均摊常数计为 $O(n^2)$,排序被双层枚举吸收。
- 空间复杂度:$O(n)$,状态数组和数值索引;排序会修改输入数组。
关键点总结
[!green]
- 初值一计入每个数单独成树的情况。
- 有序因子枚举自动处理镜像与相同孩子。
- 乘法先在宽整数中完成,再取模。
易错点总结
[!yellow]
- 状态初始化为零:所有单节点树都被漏掉。
- 只检查整除,不检查另一因子存在:可能使用数组中没有的节点值。
- 所有因子对统一乘二:两个孩子值相同时会多算。
- 只返回最大根的状态:遗漏其他根值对应的树。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 96. 不同的二叉搜索树 | 中等 | 同样把左右子树方案数相乘再累加,本题左右根还必须满足因子乘积等于当前根值。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!