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];
};