剑指 Offer(第2版)面试题 60:n 个骰子的点数
- 剑指 Offer(第2版)面试题 60:n 个骰子的点数
- 解法 1:递归
- 解法 2:动态规划
剑指 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)。