题目描述
将一个骰子投掷n次,获得的总点数为s,s的可能范围为n~6n。
掷出某一点数,可能有多种掷法,例如投掷2次,掷出3点,共有[1,2],[2,1]两种掷法。
请求出投掷n次,掷出n~6n点分别有多少种掷法。
样例
输入:n=2
输出:[1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1]
解释:投掷2次,可能出现的点数为2-12,共计11种。每种点数可能掷法数目分别为1,2,3,4,5,6,5,4,3,2,1。
所以输出[1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1]。
算法1
(DFS、暴力枚举)
- DFS跟动规一样,注重3点:
- 状态的表示,也就是状态的定义。
- 按照什么样的顺序来计算这个状态
- 边界情况,最后一次掷色子的情况,分为6类
dfs(n, s)
表示一共投了n
次色子,总和是s
的情况下,方案数是多少。dfs()
表示的就是答案,也就是要求的东西。
时间复杂度
$O(n!)$
C++ 代码
class Solution {
public:
vector<int> numberOfDice(int n) {
vector<int> res;
for(int i = n; i <= n * 6; i ++) res.push_back(dfs(n, i));
return res;
}
int dfs(int n, int sum)
{
if(sum < 0) return 0;
if(!n) return !sum;
int ans = 0;
for(int i = 1; i <= 6; i ++)
ans += dfs(n - 1, sum - i);
return ans;
}
};
算法2
(线性DP)
- DP也是,考虑状态表示和状态计算,和最后的边界情况。
f[i][j]
表示——前i次掷色子,总和是j的方案数。- 边界就是最后一次的情况,分6类,不同的类对应不同的结果。
- 三重for循环中,投掷1次,就有6种可能的点数,投掷2次,就有12种可能的点数,所以投掷n次,就有6n种可能的点数。所以在第2重循环中,
j <= i * 6
,因为可能还没到第n次,总数j也不会到6n个。 - 最后一重循环,因为最后的模型是
f[i][j] += f[i - 1][j - k]
。也就是i次中,投出1次后,变成n - 1次,总和从j变到j - k后剩余的方案数。因为是j - k
,不能越界,所以j >= k
,所以k = min(j, 6)
,可能前期j还没有枚举到6,那么k就不能取到6。 - 最后我们将计算好的答案放在res中,f[n][i],
i = n ~ 6n
,表示投掷n次后,总和分别为n~6n
的所有方案数。
时间复杂度
$O(n^2)$
C++ 代码
class Solution {
public:
vector<int> numberOfDice(int n) {
vector<int> res;
vector<vector<int> > f(n + 1, vector<int> (n * 6 + 1));
f[0][0] = 1;
/*
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n * 6; j ++)
for(int k = 1; k <= 6; k ++)
f[i][j] += f[i - 1][j - k];
*/
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= i * 6; j ++)
for(int k = 1; k <= min(j, 6); k ++)
f[i][j] += f[i - 1][j - k];
// for(int i = 1; i <= n; i ++) res.push_back(f[i][n * 6]);
for(int i = n; i <= n * 6; i ++) res.push_back(f[n][i]);
return res;
}
};
暴搜现在好像T掉了
感觉还可以从后计算减少空间复杂度
if(!n) return !sum;
这段代码是什么意思
当n等于0的时候,只有当sum为也0 才返回true(1),否则返回false(0)
你好,请问暴搜为什么是阶乘呢
组合数的规模是n!
for(int j = i; j <= i * 6; j ++) j从i开始