剑指 Offer(第2版)面试题 60:n 个骰子的点数
剑指 Offer(第2版)面试题 60:n 个骰子的点数
题目来源:80. 骰子的点数
解法 1:递归
这样做会超时。
代码:
class Solution
{
public:
vector<int> numberOfDice(int n)
{
vector<int> ans;
for (int i = n; i <= 6 * n; i++)
ans.push_back(dfs(n, i));
return ans;
}
// 辅函数 - 递归计算 n 个骰子丢出 point 点数的方案数
int dfs(int n, int point)
{
if (n < 0 || point < 0)
return 0;
if (point == 0)
return n == 0;
int count = 0;
for (int i = 1; i <= 6; i++)
count += dfs(n - 1, point - i);
return count;
}
};
复杂度分析:
时间复杂度:O(n!),其中 n 是骰子的个数。
空间复杂度:O(n),其中 n 是骰子的个数。
解法 2:动态规划
设 dp[i][j] 为丢 i 个骰子掷出 j 点的方案数。
初始化:dp[1][j] = 1,1<=j<=6。
代码:
class Solution
{
public:
vector<int> numberOfDice(int n)
{
if (n == 0)
return {};
// dp[i][j] 为丢 i 个骰子掷出 j 点的方案数
vector<vector<int>> dp(n + 1, vector<int>(6 * n + 1, 0));
// 初始化
for (int j = 1; j <= 6; j++)
dp[1][j] = 1;
// 状态转移
for (int i = 2; i <= n; i++)
for (int j = i; j <= 6 * i; j++)
{
// 状态转移方程
for (int k = 1; k <= 6; k++)
if (j - k >= 0)
dp[i][j] += dp[i - 1][j - k];
}
vector<int> ans;
for (int i = n; i <= 6 * n; i++)
ans.push_back(dp[n][i]);
return ans;
}
};
复杂度分析:
时间复杂度:O(n2),其中 n 是骰子的个数。
空间复杂度:O(n2),其中 n 是骰子的个数。动态规划数组的空间开销是 O(n) * O(6n) = O(6n2)。
空间优化:
由于我们只需要用到最后一次的结果,因此为了节省空间可以使用滚动数组,将二维 dp 数组变为一维。
代码:
class Solution
{
public:
vector<int> numberOfDice(int n)
{
if (n == 0)
return {};
vector<int> dp(6 * n + 1, 0);
// 初始化
for (int i = 1; i <= 6; i++)
dp[i] = 1;
// 状态转移
for (int i = 2; i <= n; i++)
{
for (int j = 6 * i; j >= 0; j--)
{
dp[j] = 0;
for (int k = 6; k >= 1; k--)
{
// 最后一个骰子可以扔1-6点
if (j - k >= 0)
dp[j] += dp[j - k];
}
}
}
vector<int> res(dp.begin() + n, dp.end()); // 扔n个骰子的和为[n, 6*n]
return res;
}
};
复杂度分析:
时间复杂度:O(n2),其中 n 是骰子的个数。
空间复杂度:O(n),其中 n 是骰子的个数。动态规划数组的空间开销是 O(6 * n + 1)。