DFS暴搜
和记忆化搜索类似
状态含义:dfs(n, s),投了n次,总和是s的方案数
搜索顺序:第n次投的数是i(1~6),所有方案数只要将dfs(n-1,s-i)累加
边界:
当总和为负数,方案数为0
if (sum < 0) return 0;
当投了0次,总和为0,算一种合法情况,否则不合法
if (n == 0) return !sum;
会超时,因为枚举了所有方案
n是骰子数
最简单的dfs(n, n),也要枚举n^2个状态,5n个数据,复杂度是O(n^3)
问题是因为,每次dfs都要从头开始算,已经计算过的中间过程没有保存下来
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 == 0) return !sum;
int res = 0;
// 这里可以优化一下,i <= min(sum, 6)
for (int i = 1; i <= 6; i ++)
res += dfs(n - 1, sum - i);
return res;
}
};
线性DP
状态表示:f[i, j]前i次,总和是j的方案数
状态计算:
根据最后一次投的点数分成6种情况
f[i, j] += f[i - 1][j - k]
边界情况:投0次,总和为0,唯一一种情况,,f[0][0] = 1
将所有情况都保存下来,每个情况只用计算一次
复杂度O(n^2)
题解用滚动数组将二维DP压缩为一维DP:
https://www.acwing.com/solution/content/852/
class Solution {
public:
vector<int> numberOfDice(int n) {
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 <= i * 6; j ++)
// 这里用min(j, 6)代替6
// 意思是当前点数k和不能超过总和j
// 一方面是优化,减少了计算次数
// 另一方面是避免出现负数,使状态矩阵越界
for (int k = 1; k <= min(j, 6); k ++)
f[i][j] += f[i - 1][j - k];
vector<int> res;
for (int i = n; i <= n * 6; i ++) res.push_back(f[n][i]);
return res;
}
};