首先看题目
这里有 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 取模。
在答题开始之前,为了寻找到解题思路,我特意制作了一张表寻找数字之间的规律
我们现在假定骰子有6个面(即k=6),一共有4个骰子(即n=4),得到总数为14(target=14)有多少种可能
根据图表我们可以很清楚的得到答案为146,可是这个数字与其他数字有什么关联呢? 我们做如下假定: 如果最后一颗骰子的点数为1,那么前三颗骰子必须掷出13点(即n=3,target=13) 如果最后一颗骰子的点数为2,那么前三颗骰子必须掷出12点(即n=3,target=12) 如果最后一颗骰子的点数为3,那么前三颗骰子必须掷出11点(即n=3,target=11) 如果最后一颗骰子的点数为4,那么前三颗骰子必须掷出10点(即n=3,target=10) 如果最后一颗骰子的点数为5,那么前三颗骰子必须掷出9点(即n=3,target=9) 如果最后一颗骰子的点数为6,那么前三颗骰子必须掷出8点(即n=3,target=8)
那么,我把这六种可能加起来,不就可以得到n=4,target=14的解吗, 接下来我们顺着这个思路继续找规律,为了方便后续解读,我把这个表的结果定义为f[n][t] 根据上面的思路我们可以得到:
f[n][t]=f[n-1][t-1]+f[n-1][t-2]+……+f[n-1][t-k]
这时候,我们发现如果k的值特别大的时候,这个函数的计算量也特别的打,而且不可避免的有重复计算,比如:
f[4][14]=f[3][13]+f[3][12]+f[3][11]f[3][10]f[3][9]f[3][8]
f[4][13]=f[3][12]+f[3][11]f[3][10]f[3][9]f[3][8]+f[3][7]
在上面的两个例子中f[3][12]+f[3][11]f[3][10]f[3][9]f[3][8]被重复的计算了,为了减少函数的计算量,我们做出以下优化
f[4][14]=f[4][13]+f[3][13]-f[3][7]
即f[n][t]=f[n][t-1]+f[n-1][t-1]-f[t-1][t-k-1]
同时我们通过观察得出:
当n=4时,target=4的结果与target=24的结果相等
target=5的结果与target=23的结果相等
target=6的结果与target=22的结果相等
……
也就是说:数组的第一项和最后一项相等,第二项与倒数第二项相等,一直到数组的最中间
接下来
我们用代码稍微实现一下
/**
* @param {number} n
* @param {number} k
* @param {number} target
* @return {number}
*/
var numRollsToTarget = function(n,k,target) {
const max = n*k;
if (target < n || target > max) {
return 0; // 无法组成 target
}
const MOD = 1_000_000_007;
const f = new Array();
f[0]=new Array(k).fill(1)
for (let i = 1; i < n; i++) {
f[i]=[1]
let maxIndex = (i+1)*(k-1)
f[i][maxIndex]=1
for (let j = 1; j <= maxIndex/2+1; j++) {
if(f[i][j]) continue;
let number = j-k>=0
?(f[i][j-1]+f[i-1][j]-f[i-1][j-k]) % MOD
:(f[i][j-1]+f[i-1][j]) % MOD
f[i][j] = number
f[i][maxIndex-j] = number
}
}
return (f[n-1][target-n]+2*MOD) % MOD;//避免出现负数的情况
};
至此,题目解答已完成
当我们遇到动态规划问题的时候,通常的思路是从最后一步开始,一步一步的往前推导,最终把复杂问题化为简单问题,才能最终解决问题。