leetcode
leetcode 2201 ~ 2250
获得分数的方法数

获得分数的方法数

难度:

标签:

题目描述

There is a test that has n types of questions. You are given an integer target and a 0-indexed 2D integer array types where types[i] = [counti, marksi] indicates that there are counti questions of the ith type, and each one of them is worth marksi points.

Return the number of ways you can earn exactly target points in the exam. Since the answer may be too large, return it modulo 109 + 7.

Note that questions of the same type are indistinguishable.

  • For example, if there are 3 questions of the same type, then solving the 1st and 2nd questions is the same as solving the 1st and 3rd questions, or the 2nd and 3rd questions.

 

Example 1:

Input: target = 6, types = [[6,1],[3,2],[2,3]]
Output: 7
Explanation: You can earn 6 points in one of the seven ways:
- Solve 6 questions of the 0th type: 1 + 1 + 1 + 1 + 1 + 1 = 6
- Solve 4 questions of the 0th type and 1 question of the 1st type: 1 + 1 + 1 + 1 + 2 = 6
- Solve 2 questions of the 0th type and 2 questions of the 1st type: 1 + 1 + 2 + 2 = 6
- Solve 3 questions of the 0th type and 1 question of the 2nd type: 1 + 1 + 1 + 3 = 6
- Solve 1 question of the 0th type, 1 question of the 1st type and 1 question of the 2nd type: 1 + 2 + 3 = 6
- Solve 3 questions of the 1st type: 2 + 2 + 2 = 6
- Solve 2 questions of the 2nd type: 3 + 3 = 6

Example 2:

Input: target = 5, types = [[50,1],[50,2],[50,5]]
Output: 4
Explanation: You can earn 5 points in one of the four ways:
- Solve 5 questions of the 0th type: 1 + 1 + 1 + 1 + 1 = 5
- Solve 3 questions of the 0th type and 1 question of the 1st type: 1 + 1 + 1 + 2 = 5
- Solve 1 questions of the 0th type and 2 questions of the 1st type: 1 + 2 + 2 = 5
- Solve 1 question of the 2nd type: 5

Example 3:

Input: target = 18, types = [[6,1],[3,2],[2,3]]
Output: 1
Explanation: You can only earn 18 points by answering all questions.

 

Constraints:

  • 1 <= target <= 1000
  • n == types.length
  • 1 <= n <= 50
  • types[i].length == 2
  • 1 <= counti, marksi <= 50

代码结果

运行时间: 216 ms, 内存: 18.6 MB


/*
题目思路:
使用Java Stream API来实现动态规划。我们依然定义dp数组用于存储达到各分数的方案数。
通过Stream来进行数组的迭代和更新。
*/

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

public class Solution {
    public int numOfWays(int target, int[][] types) {
        int MOD = 1000000007;
        int[] dp = new int[target + 1];
        dp[0] = 1;
        for (int[] type : types) {
            int count = type[0];
            int marks = type[1];
            int[] newDp = Arrays.copyOf(dp, dp.length);
            IntStream.range(0, target + 1).forEach(i -> {
                IntStream.rangeClosed(1, count).forEach(k -> {
                    if (i + k * marks <= target) {
                        newDp[i + k * marks] = (newDp[i + k * marks] + dp[i]) % MOD;
                    }
                });
            });
            dp = newDp;
        }
        return dp[target];
    }
}

解释

方法:

本题解使用动态规划来解决这个问题。我们定义 dp[i][j] 为考虑到第 i 种类型的题目时,恰好得到 j 分的方法数。初始状态 dp[0][0] = 1,意味着不选择任何题目时,得分为 0 的方法有一种。对于每种类型的题目,我们更新 dp 数组,计算使用这种类型的题目可以获得的不同分数的方法数。对于每种类型的题目 types[i] = [counti, marksi],我们需要处理可选数量和分值情况,利用前缀和思想优化计算过程,防止重复计算。最终,dp[n][target] 将给出恰好得到 target 分的方法数。

时间复杂度:

O(n*target)

空间复杂度:

O(n*target)

代码细节讲解

🦆
在使用动态规划解决这个问题时,为什么需要插入一个哑节点types[0] = [0, 0],这对解题有什么帮助?
插入一个哑节点types[0] = [0, 0]主要是为了处理边界情况,使得数组的索引与题目类型对应起来,方便计算。哑节点的引入使得dp数组的初始化更加直观,dp[0][0] = 1表示在不选择任何题目的情况下,得分为0的方法恰好有一种。这样设置后,可以避免在循环中进行复杂的边界判断,简化代码逻辑。
🦆
dp数组的初始化为什么只将dp[0][0]设为1,而其他dp[0][j](j>0)的初值是什么?
dp数组的初始化中dp[0][0]设为1是因为在不选择任何题目的情况下,得分为0的方法只有一种,即什么都不做。而对于dp[0][j](j>0),它们的初值都应该是0,因为在不使用任何题目的情况下,不可能获得大于0的分数,因此得分为j(j>0)的方法数应为0。
🦆
请解释在动态规划中,递推公式中的`dp[i][j] = (dp[i][j] - dp[i - 1][j - (cnt + 1) * mark] + mod) % mod`这一步是如何避免重复计算的?
递推公式中的`dp[i][j] = (dp[i][j] - dp[i - 1][j - (cnt + 1) * mark] + mod) % mod`这一步用于确保不会重复计算超过允许数量的题目。这里,`dp[i - 1][j - (cnt + 1) * mark]`代表使用超过允许的题目数量来达到某个分数的方法数,通过从当前累计值中减去这部分,确保每种类型的题目最多只被使用其允许的次数。`mod`用于处理可能出现的负数,保证结果始终为非负。
🦆
在更新dp数组时,为什么需要使用模运算mod?
在更新dp数组时使用模运算mod是为了防止计算结果溢出并保持结果在合理的范围内。由于方法数可能非常大,直接计算和存储这些值可能导致整数溢出,使用模运算可以保证每一步的结果都在0到mod-1之间,确保计算结果的稳定性和准确性。此外,这也是一种常见的技术,用于满足竞赛和实际应用中的需求,以防止在大数运算中的数值问题。

相关问题