Given n dice, each with k faces numbered from 1 to k, you are to find out in how many ways you can roll the dice so that the sum of the face-up numbers equals the given target. The answer can be large, so return it modulo 109 + 7.
n: the number of dice.k: the number of faces on each die (faces are 1 to k).target: the required sum of the dice faces.Constraints:
k on its face.n dice.109 + 7.
At first glance, you might think of generating all possible outcomes for rolling n dice and counting those whose sum is target. But with increasing n and k, the number of combinations grows exponentially, making brute-force infeasible.
Instead, notice that this is a classic combinatorial problem where order does not matter, but the sum does. This hints at using dynamic programming (DP): for each die, keep track of the number of ways to reach every possible sum up to target. By breaking the problem into smaller, overlapping subproblems and storing intermediate results, we avoid redundant calculations and make the solution efficient.
We'll use a dynamic programming approach with the following steps:
dp[i][t] be the number of ways to get sum t using i dice.
dp[0][0] = 1.
n), and for each possible sum (from 1 to target), try every possible face value (f from 1 to k). If t - f is non-negative, then add dp[i-1][t-f] to dp[i][t].
dp[n][target].
109 + 7.
We use a 2D array for DP, but it can be optimized to 1D since each state only depends on the previous number of dice.
Let's work through an example: n = 2, k = 6, target = 7.
dp[1][s] is 1 for s from 1 to 6 (each face), 0 otherwise.dp[1][7-f] to dp[2][7] if 7-f is between 1 and 6.dp[1][7-f] is 1 if 7-f is 1..6.dp[2][7] = 1 + 1 + 1 + 1 + 1 + 1 = 6.The DP table correctly counts all combinations.
Brute-force: Would require checking k^n combinations, which is exponential and infeasible for large n.
Dynamic Programming:
O(n * target * k) because for each of the n dice and each possible sum up to target, we try all k faces.O(n * target) for the DP table. This can be optimized to O(target) with a rolling array.The problem is a classic example of using dynamic programming to count the number of ways to achieve a sum with dice rolls. By breaking the problem into smaller subproblems and building up solutions, we avoid the exponential blowup of brute-force approaches. The DP approach is efficient, elegant, and easily generalizable to similar counting problems.
MOD = 10**9 + 7
class Solution:
def numRollsToTarget(self, n: int, k: int, target: int) -> int:
dp = [0] * (target + 1)
dp[0] = 1
for dice in range(1, n + 1):
new_dp = [0] * (target + 1)
for t in range(1, target + 1):
for f in range(1, k + 1):
if t - f >= 0:
new_dp[t] = (new_dp[t] + dp[t - f]) % MOD
dp = new_dp
return dp[target]
class Solution {
public:
int numRollsToTarget(int n, int k, int target) {
const int MOD = 1e9 + 7;
vector<int> dp(target + 1, 0);
dp[0] = 1;
for (int dice = 1; dice <= n; ++dice) {
vector<int> new_dp(target + 1, 0);
for (int t = 1; t <= target; ++t) {
for (int f = 1; f <= k; ++f) {
if (t - f >= 0) {
new_dp[t] = (new_dp[t] + dp[t - f]) % MOD;
}
}
}
dp = new_dp;
}
return dp[target];
}
};
class Solution {
public int numRollsToTarget(int n, int k, int target) {
int MOD = 1000000007;
int[] dp = new int[target + 1];
dp[0] = 1;
for (int dice = 1; dice <= n; dice++) {
int[] new_dp = new int[target + 1];
for (int t = 1; t <= target; t++) {
for (int f = 1; f <= k; f++) {
if (t - f >= 0) {
new_dp[t] = (new_dp[t] + dp[t - f]) % MOD;
}
}
}
dp = new_dp;
}
return dp[target];
}
}
var numRollsToTarget = function(n, k, target) {
const MOD = 1e9 + 7;
let dp = new Array(target + 1).fill(0);
dp[0] = 1;
for (let dice = 1; dice <= n; dice++) {
let new_dp = new Array(target + 1).fill(0);
for (let t = 1; t <= target; t++) {
for (let f = 1; f <= k; f++) {
if (t - f >= 0) {
new_dp[t] = (new_dp[t] + dp[t - f]) % MOD;
}
}
}
dp = new_dp;
}
return dp[target];
};