LeetCode 1155. Number of Dice Rolls With Target Sum

[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];
    }
    
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值