leetcode
leetcode 701 ~ 750
带因子的二叉树

带因子的二叉树

难度:

标签:

题目描述

代码结果

运行时间: 58 ms, 内存: 17.5 MB


/*
 * 思路:
 * 1. 对数组进行排序,确保在处理每个元素时,其所有可能的子节点都已经处理过。
 * 2. 使用动态规划的方法。dp[i] 表示以 arr[i] 为根节点的二叉树的数量。
 * 3. 使用 Java Stream API 进行处理。
 * 4. 对于每一个元素 arr[i],检查其所有可能的因子 arr[j] 和 arr[k],
 *    其中 arr[i] = arr[j] * arr[k] 并且 arr[j] 和 arr[k] 也必须在数组中。
 * 5. 对于每一个符合条件的因子组合,dp[i] += dp[j] * dp[k]。
 * 6. 返回 dp 数组所有元素的和,并对 10^9 + 7 取余。
 */

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

public class BinaryTreeCountStream {
    public int numFactoredBinaryTrees(int[] arr) {
        int MOD = 1_000_000_007;
        Arrays.sort(arr);
        long[] dp = new long[arr.length];
        Arrays.fill(dp, 1);
        Map<Integer, Integer> index = IntStream.range(0, arr.length).boxed().collect(Collectors.toMap(i -> arr[i], i -> i));
        IntStream.range(0, arr.length).forEach(i -> {
            IntStream.range(0, i).forEach(j -> {
                if (arr[i] % arr[j] == 0) { // arr[j] is a factor of arr[i]
                    int right = arr[i] / arr[j];
                    if (index.containsKey(right)) {
                        dp[i] = (dp[i] + dp[j] * dp[index.get(right)]) % MOD;
                    }
                }
            });
        });
        long ans = Arrays.stream(dp).sum();
        return (int) (ans % MOD);
    }

    public static void main(String[] args) {
        BinaryTreeCountStream btc = new BinaryTreeCountStream();
        int[] arr = {2, 4, 5, 10};
        System.out.println(btc.numFactoredBinaryTrees(arr)); // Output: 7
    }
}

解释

方法:

这个题解的思路是首先对输入数组进行排序,然后用哈希表记录每个数的因子对。接着使用记忆化搜索,对于每个数,如果它没有因子对,则只有它自己这一种情况;如果有因子对,则结果是它的每个因子对的情况数相乘再相加。最后对每个数的情况数求和并取模即可。

时间复杂度:

O(n^2)

空间复杂度:

O(n^2)

代码细节讲解

🦆
为什么在题解中首先对输入数组进行排序?排序对解题有什么帮助?
在题解中对输入数组进行排序的原因是为了简化因子对的查找过程。排序后,数组中的每个元素都按照从小到大的顺序排列,这样在遍历数组以构建因子对的哈希表时,可以更有效地确定哪些数可以组成因子对。此外,排序还允许使用二分查找来加速查找过程,虽然在这个具体的题解中没有直接使用二分查找,但排序本身对于确定因子对的上界和下界是有帮助的。例如,如果已知一个数的平方已经大于数组中的最大数,则可以直接停止内层循环,避免不必要的计算。
🦆
在构建哈希表时,为什么需要检查当前数的平方是否大于数组中的最大数,这个优化的具体逻辑是什么?
这个优化的逻辑基于一个事实:如果某个数的平方已经大于数组中的最大数,那么这个数与数组中任何数的乘积也必然大于数组中的最大数。因此,当发现当前数的平方超过了数组中的最大数时,就可以停止考虑以这个数作为因子对中一个因子的可能性,从而跳出循环。这种优化减少了不必要的迭代和计算,提高了整体算法的效率,尤其是在数组中包含较大数值时更为明显。
🦆
题解中使用了`@cache`进行记忆化搜索,具体来说,这种方法在此问题中解决了哪些性能问题?
使用`@cache`进行记忆化搜索可以大幅提升性能,主要是通过避免重复计算已经计算过的结果。在这个问题中,每个数的因子对可能会在求解其他数的因子对情况时被重复计算多次。通过记忆化搜索,一旦某个数的所有因子对的情况数被计算过,就会存储起来,后续的搜索可以直接使用存储的结果,无需再次进行复杂的计算。这样大大减少了计算量,尤其是在面对较大的输入数组时,能有效避免算法的时间复杂度指数级增加。
🦆
对于乘积大于数组中最大值的情况直接跳出循环是基于什么考虑?会不会遗漏某些有效的因子对?
基于的考虑是效率和计算的精确性。当发现某个因子对的乘积超过数组中的最大数时,这个乘积不可能作为有效的因子对出现在任何结果中,因为数组中不存在这样的数。因此,继续计算这种情况下的乘积是不必要的,直接跳出循环可以节省计算资源。这种做法不会遗漏有效的因子对,因为所有有效的因子对的乘积必须是数组中已存在的数。

相关问题