[Med] LeetCode 1155. Number of Dice Rolls With Target Sum
链接: https://leetcode.com/problems/number-of-dice-rolls-with-target-sum/
题目描述:
You have d dice, and each die has f faces numbered 1, 2, …, f.
Return the number of possible ways (out of fd total ways) modulo 10^9 + 7 to
roll the dice so the sum of the face up numbers equals target.
Example 1:
Input: d = 1, f = 6, target = 3
Output: 1
Explanation:
You throw one die with 6 faces. There is only one way to get a sum of 3.
Example 2:
Input: d = 2, f = 6, target = 7
Output: 6
Explanation:
You throw two dice, each with 6 faces. There are 6 ways to get a sum of 7:
1+6, 2+5, 3+4, 4+3, 5+2, 6+1.
Example 3:
Input: d = 2, f = 5, target = 10
Output: 1
Explanation:
You throw two dice, each with 5 faces. There is only one way to get a sum of 10: 5+5.
Example 4:
Input: d = 1, f = 2, target = 3
Output: 0
Explanation:
You throw one die with 2 faces. There is no way to get a sum of 3.
Example 5:
Input: d = 30, f = 30, target = 500
Output: 222616187
Explanation:
The answer must be returned modulo 10^9 + 7.
Constraints:
- 1 <= d, f <= 30
- 1 <= target <= 1000
Tag: Dynamic Programming
解题思路
这里有 d 个一样的骰子,每个骰子上都有 f 个面,分别标号为 1, 2, …,f。我们约定:掷骰子的得到总点数为各骰子面朝上的数字的总和。如果需要掷出的总点数为 target,计算出有多少种不同的组合情况。首先我们一定要用到所有的d个骰子。我们可以把投掷筛子的过程描述为以下的行为。我们投掷第一个骰子,查看记录每一个骰子可能出现的面。我们假设骰子有五个面,f=5。
第一个骰子投掷的时候可能出现的数值有[1,2,3,4,5]。
第二个骰子朝上的可能性还是1到5。这五个可能性要跟第一个骰子出现的所有可能性进行交叉相乘。如果第一个骰子出现的是1的话,加上第二个骰子可能出现。[1+1,1+2,1+3,1+4,1+5]
如果第一个骰子出现的是2的话,加上第二个骰子可能出现。[2+1,2+2,2+3,2+4,2+5]
如果第一个骰子出现的是3的话,加上第二个骰子可能出现。[3+1,3+2,3+3,3+4,3+5]
如果第一个骰子出现的是4的话,加上第二个骰子可能出现。[4+1,4+2,4+3,4+4,4+5]
如果第一个骰子出现的是5的话,加上第二个骰子可能出现。[5+1,5+2,5+3,5+4,5+5]
以上所有的数值都是两个骰子可能会产生的组合。每一个值可能对应不止一种组合方式。
如果此时还有第三枚骰子的话,会基于上面所有的组合下,再分别跟[1,2,3,4,5]进行组合。
以此类推,当我们投掷完所有的d枚骰子,我们就可以检查所有可能的组合当中,是否可以组成我们想要的target的值。如果可以的话,总共的组合形式有多少种。
解法一:
TIME: d* f(中间值的个数)
SPACE: 取决于中间最大层的不同组合的个数
class Solution {
public int numRollsToTarget(int d, int f, int target) {
if(target == 0) return 0;
int res =0;
Map<Integer, Integer> map = new HashMap<>();
for(int i=1; i<=f; i++){
if(i == target && d == 1) res++;
map.put(i, 1);
}
for(int i=2; i<=d; i++){
Map<Integer, Integer> temp = new HashMap<>();
for(Map.Entry<Integer, Integer> entry: map.entrySet()){
int key = entry.getKey(), val = entry.getValue();
for(int j=1; j<=f; j++){
if(key+j > target) continue;
temp.put(key+j, (temp.getOrDefault(key+j, 0)+val)%1000000007);
}
}
map = temp;
}
return map.getOrDefault(target, 0);
}
}
解法二:
上面一种方式相对来说会比较慢,因为计算了很多的冗余值。比如说中间如果我们已经发现某一个组合的值超过了target的值,实际上后面我们都完全不需要计算这个组合的和了。因为骰子没有负数,只会增长不会减少。
我们根据这个性质,可以发现我们最多只需要一个宽度为target的数组就可以了。任何超过这个数组长度的数字我们都不需要。
TIME: O(D* F* Target)
SPACE: O(D *target)
class Solution {
public int numRollsToTarget(int d, int f, int target) {
if(target == 0) return 0;
int[][] dp = new int[d+1][target+1];
dp[0][0] =1;
for(int i=1; i<= d; i++){
for(int j=1; j<= target; j++){
if(d*f < j) continue;
for(int p = 1; p<=f && p <= j; p++){
dp[i][j] = (dp[i][j]+dp[i-1][j-p]) % 1000000007;
}
}
}
return dp[d][target];
}
}
解法三:
可以进一步的化简空间,从O(d*target)变为O(target)
class Solution {
public int numRollsToTarget(int d, int f, int target) {
if(target < d) return 0;
int[] dp = new int[target+1];
/*
注意这里是face和target之间取一个小值。有可能face小于target,这样不是每一位都可以初始化为1的。
*/
for(int i=1; i<=f && i<=target; i++) dp[i] =1;
d--;
while(d-- >0){
int[] cur = new int[target+1];
for(int i=1; i<=target; i++){
for(int j=1; j<=f; j++){
//下面的语句和被comment掉的语句作用一样
if(i-j <=0) continue;
cur[i] += dp[i-j];
cur[i] %= 1000000007;
//if(i+j > target) continue;
//cur[i+j] += dp[i];
//cur[i+j] %= 1000000007;
}
}
//将前一层和当前层交换
dp = cur;
}
return dp[target];
}
}
解法四:
这里还有一种神奇的O(d*target)解法,我还没看懂。但是先贴在这里了
class Solution {
public int numRollsToTarget(int d, int f, int target) {
int mod = 1_000_000_007;
int[] dpCur = new int[target + 1], dpNext = new int[target + 1];
for (int i = 1; i <= Math.min(f, target); i++)
dpCur[i] = 1;
for (int dd = 1; dd < d; dd++) {
int sum = 0;
for (int t = 1; t <= target; t++) {
sum += dpCur[t - 1];
if (t - f - 1 >= 0) {
sum-=dpCur[t - f - 1];
if (sum < 0)
sum += mod;
}
sum %= mod;
dpNext[t] = sum;
}
dpCur = dpNext;
dpNext = new int[target + 1];
}
return dpCur[target];
}
}
转载:https://blog.csdn.net/MrJustin/article/details/105722590