leetcode刷题:1155_掷骰子等于目标和的方法数

题目描述

这里有 n 个一样的骰子,每个骰子上都有 k 个面,分别标号为 1 到 k 。给定三个整数 n , k 和 target ,返回可能的方式(从总共 kn 种方式中)滚动骰子的数量,使正面朝上的数字之和等于 target 。

答案可能很大,你需要对 109 + 7 取模

示例 1:

输入:n = 1, k = 6, target = 3
输出:1
解释:你扔一个有 6 个面的骰子。
得到 3 的和只有一种方法。

示例 2:

输入:n = 2, k = 6, target = 7
输出:6
解释:你扔两个骰子,每个骰子有 6 个面。
得到 7 的和有 6 种方法:1+6 2+5 3+4 4+3 5+2 6+1。

示例 3:

输入:n = 30, k = 30, target = 500
输出:222616187
解释:返回的结果必须是对 109 + 7 取模。

提示:

1 <= n, k <= 30
1 <= target <= 1000

官方题解(动态规划)

来源:https://leetcode.cn/problems/number-of-dice-rolls-with-target-sum/solutions/2490436/zhi-tou-zi-deng-yu-mu-biao-he-de-fang-fa-eewv/

我们可以使用动态规划解决本题。

记 f(i, j) 表示使用 i 个骰子且数字之和为 j 的方案数。为了计算该值,我们可以枚举最后一个骰子的数字,它的范围为 [1,k],那么状态转移方程即为:
f ( i , j ) = ∑ x = 1 k f ( i − 1 , j − x ) f(i, j) = \sum_{x=1}^{k} f(i-1, j-x) f(i,j)=x=1kf(i1,jx)
动态规划的边界条件为:
f ( 0 , 0 ) = 1 f(0, 0) = 1 f(0,0)=1
即我们还没有掷骰子时,数字之和为0,以及:
f ( i , j ) = 0 i f j < 0 f(i, j) = 0 \quad if\quad j < 0 f(i,j)=0ifj<0
即骰子的和不能小于0,在进行状态转移时,我们可以忽略所有 j - x < 0 的情况。
最终的答案为 f (n, target) 。

解法一

class Solution(object):
    def numRollsToTarget(self, n, k, target):
        """
        :type n: int
        :type k: int
        :type target: int
        :rtype: int
        """
        mod = 1e9 + 7
        f = [[0] * (target + 1) for _ in range(n + 1)]
        f[0][0] = 1
        for i in range(1, n + 1):
            for j in range(target + 1):
                for x in range(1, k + 1):
                    if j - x >= 0:
                        f[i][j] = (f[i][j] + f[i - 1][j - x]) % mod
        return int(f[n][target])

注意到状态转移放曾中,f(i,j)只会从 f(i−1,⋯ ) 转移而来,因此我们只需要存储当前行(第 i 行)和上一行(第 i−1 行)的值,即可以用两个一维数组代替上面代码中的二维数组 f 进行状态转移。

解法二

class Solution(object):
    def numRollsToTarget(self, n, k, target):
        """
        :type n: int
        :type k: int
        :type target: int
        :rtype: int
        """
        mod = 1e9 + 7
        f = [1] + [0] * target
        for i in range(1, n + 1):
        	g = [0] * (target + 1)
            for j in range(target + 1):
                for x in range(1, k + 1):
                    if j - x >= 0:
                        g[j] = (g[j] + f[j - x]) % mod
             f = g
        return int(f[target])

进一步,我们发现 f(i,j) 只会从第二维严格小于它的 f(i−1,⋯ ) 转移而来,因此我们可以倒序遍历第二维来计算 f(i,j),这样可以只使用一个一维数组。也就是说,当我们计算 f(i,j) 时,一维数组中下标大于 j 的位置都是 f(i,⋯ ) 的值,下标小于 j 的位置都是 f(i−1,⋯ ) 的值,这样就保证了 f(i,j) 结果的正确性。

解法三

class Solution(object):
    def numRollsToTarget(self, n, k, target):
        """
        :type n: int
        :type k: int
        :type target: int
        :rtype: int
        """
        mod = 1e9 + 7
        f = [1] + [0] * target
        for i in range(1, n + 1):
            for j in range(target, -1, -1):
                f[j] = 0
                for x in range(1, k + 1):
                    if j - x >= 0:
                        f[j] = (f[j] + f[j - x]) % mod
        return int(f[target])

**【碎碎念】动态规划出现了好多好多次,真的好好用啊!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值