leetcode
leetcode 1551 ~ 1600
生成乘积数组的方案数

生成乘积数组的方案数

难度:

标签:

题目描述

代码结果

运行时间: 69 ms, 内存: 21.7 MB


/*
 * 思路:
 * 1. 和普通Java版本类似,但尽量使用Stream来进行操作。
 * 2. 分解质因数,计算组合数,和普通解法相同。
 * 3. 通过Stream简化代码的操作。
 */

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class SolutionStream {
    private static final int MOD = 1000000007;

    public int[] waysToFillArray(int[][] queries) {
        return Arrays.stream(queries)
            .mapToInt(query -> countWays(query[0], query[1]))
            .toArray();
    }

    private int countWays(int n, int k) {
        Map<Integer, Integer> primeFactors = getPrimeFactors(k);
        return primeFactors.values().stream()
            .mapToLong(power -> combination(n + power - 1, power))
            .reduce(1L, (a, b) -> a * b % MOD)
            .intValue();
    }

    private Map<Integer, Integer> getPrimeFactors(int num) {
        return IntStream.rangeClosed(2, (int) Math.sqrt(num))
            .filter(i -> num % i == 0)
            .collect(HashMap::new, (map, i) -> {
                while (num % i == 0) {
                    map.put(i, map.getOrDefault(i, 0) + 1);
                    num /= i;
                }
            }, HashMap::putAll);
    }

    private long combination(int n, int k) {
        return IntStream.range(0, k)
            .mapToLong(i -> (long)(n - i) * modInverse(i + 1, MOD) % MOD)
            .reduce(1L, (a, b) -> a * b % MOD);
    }

    private long modInverse(long a, int mod) {
        return power(a, mod - 2, mod);
    }

    private long power(long a, int b, int mod) {
        long res = 1;
        while (b > 0) {
            if ((b & 1) != 0) res = res * a % mod;
            a = a * a % mod;
            b >>= 1;
        }
        return res;
    }
}

解释

方法:

首先,题解使用了埃拉托斯特尼筛法来找出10^4范围内的所有质数,并标记每个数的最小质因数。然后,对于每个1到10^4的数,使用最小质因数将其分解,记录其质因数的幂次形成一个元组,存储于字典d中。对于每个查询[ni, ki],基于ki的质因数分解(从字典d中获取),使用组合数学计算将k分解为n个因子的方法数。具体计算方式是使用多项式定理中的多重集合公式来处理每个质因子的分配,其中涉及到的组合数可以通过math.comb函数高效计算。结果对10^9+7取余确保结果在规定范围内。

时间复杂度:

O(m * f)

空间复杂度:

O(n + m)

代码细节讲解

🦆
在题解中提到使用埃拉托斯特尼筛法来找出10^4范围内的所有质数,这个范围是如何确定的?
这个范围的确定是基于问题的需求,即处理的数值范围是1到10^4的数。使用埃拉托斯特尼筛法可以高效地筛选出10^4范围内的所有质数,并在这个过程中标记每个数的最小质因数。这样的范围保证了不仅可以获取所有需要的质数,还能够对1到10^4中的每个数进行快速的质因数分解,因为每个数的最小质因数已经在筛选过程中被记录。这个范围是针对题目给定的最大输入范围来选取的,以确保算法的应用面和效率。
🦆
在处理质因数分解的过程中,题解说明使用了collections.defaultdict(int)来计数,为什么选择这种数据结构,而不是使用普通的字典或其他数据结构?
在质因数分解的过程中,使用collections.defaultdict(int)可以自动处理不存在的键的情况,即当一个新的质因数被发现时,不需要额外的代码来检查和初始化字典键。如果使用普通字典,每次添加一个新的质因数时都需要先检查键是否存在,然后进行初始化,这会增加代码的复杂性和运行时的键查找成本。defaultdict通过自动为未知键提供一个默认的整数值(0),简化了计数的过程,并提高了代码的可读性和效率。
🦆
为什么在计算组合数时选择使用math.comb函数,并且在计算过程中立即取余,这样做的优点是什么?
使用math.comb函数计算组合数是因为它直接提供了一个高效且准确的方式来计算大数的组合数,避免了复杂的自实现和可能的错误。在计算过程中立即取余是因为组合数的结果可能非常大,直接操作这些大数会导致性能下降和可能的整数溢出。通过在每一步计算后立即取余,结果保持在一个可管理的范围内,同时也符合题目要求的模10^9+7的结果,这有助于防止溢出并保持计算的高效性。
🦆
在计算组合数时,如果n或k的数值非常大,是否会影响math.comb函数的性能或结果的精度?
math.comb函数是设计来高效并准确计算组合数的,它可以处理相对较大的数值。然而,如果n或k的值非常大,计算的复杂度和所需的处理时间会增加,这可能影响性能。但在本题中,由于n和k的大小受到10^4的限制,使用math.comb不会引起性能问题。关于精度,math.comb利用Python的内置长整型进行计算,可以确保结果的正确性,不会有精度损失。

相关问题