题目描述
把 n 个骰子扔在地上,求点数和为 s 的概率。
解题思路
【C++解法】
1 递归大法
重复计算,计算复杂度大。
2 循环大法
a 动态规划
空间复杂度:O(N^2)
使用一个二维数组 dp 存储点数出现的次数,其中 dp[i][j] 表示前 i 个骰子产生点数 j 的次数。
class Solution {
public:
/**
* @param n an integer
* @return a list of pair<sum, probability>
*/
vector<pair<int, double>> dicesSum(int n) {
const int face = 6;
const int pointNum = face*n;
vector< vector<long> > dp(n+1, vector<long>(pointNum+1));
for(int i=1; i<=face; ++i) dp[1][i] = 1;
for(int i=2; i<=n; ++i)
for(int j=i; j<=pointNum; ++j) /* 使用 i 个骰子最小点数为 i */
for(int k=1; k<=face && k<=j; ++k)
dp[i][j] += dp[i-1][j-k];
const double totalNum = pow(6, n);
vector<pair<int, double>> res;
for(int i=n; i<= pointNum; ++i) res.push_back(make_pair(i, dp[n][i]/totalNum));
return res;
}
};
b 动态规划+旋转数组
空间复杂度:O(N)
class Solution {
public:
vector<pair<int, double>> dicesSum(int n) {
const int face = 6;
const int pointNum = face*n;
vector< vector<long> > dp(2, vector<long>(pointNum+1));
for(int i=1; i<=face; ++i) {
dp[0][i] = 1;
}
int flag = 1; /* 旋转标记 */
for (int i=2; i<=n; i++, flag=1-flag) {
for (int j=0; j<=pointNum; j++) dp[flag][j] = 0; /* 旋转数组清零 */
for (int j=i; j<=pointNum; j++)
for (int k=1; k<=face && k<=j; k++)
dp[flag][j] += dp[1-flag][j-k];
}
const double totalNum = pow(6, n);
vector<pair<int, double>> res;
for(int i=n; i<= pointNum; ++i) res.push_back(make_pair(i, dp[1-flag][i]/totalNum));
return res;
}
};
上面这种方法容易数据溢出,可以考虑下面的方法:
class Solution {
public:
/**
* @param n an integer
* @return a list of pair<sum, probability>
*/
vector<pair<int, double>> dicesSum(int n) {
vector<pair<int, double>> res;
int face = 6;
int maxPoint = face * n;
vector< vector<double>> dp(n+1, vector<double>(maxPoint+1));
for (int i=1; i<=face; i++) {dp[1][i] = 1.0 / face;}
for (int i=2; i<=n; i++) {
int subMaxPoint = i * face;
for (int j=i; j<=maxPoint; j++) {
for (int k=1; k<=face && k<=j; k++) {
dp[i][j] += dp[i-1][j-k] / face;
}
}
}
for (int i=n; i<=maxPoint; i++) {
res.push_back(make_pair(i, dp[n][i]));
}
return res;
}
};
同理,旋转数组:
class Solution {
public:
/**
* @param n an integer
* @return a list of pair<sum, probability>
*/
vector<pair<int, double>> dicesSum(int n) {
vector<pair<int, double>> res;
int face = 6;
int maxPoint = face * n;
int flag = 1;
vector< vector<double>> dp(2, vector<double>(maxPoint+1));
for (int i=1; i<=face; i++) {dp[0][i] = 1.0 / face;}
for (int i=2; i<=n; i++, flag=1-flag) {
int subMaxPoint = i * face;
for (int j=0; j<=maxPoint; j++) {dp[flag][j] = 0;}
for (int j=i; j<=subMaxPoint; j++) {
for (int k=1; k<=face && k<=j; k++) {
dp[flag][j] += dp[1-flag][j-k] / face;
}
}
}
for (int i=n; i<=maxPoint; i++) {
res.push_back(make_pair(i, dp[1-flag][i]));
}
return res;
}
};
【Java解法】
1. 二维动态规划
public class Solution {
/**
* @param n an integer
* @return a list of Map.Entry<sum, probability>
*/
public List<Map.Entry<Integer, Double>> dicesSum(int n) {
int face = 6;
int maxPoint = 6 * n;
double[][] dp = new double[n+1][maxPoint+1];
for (int i=1; i<=face; i++) {dp[1][i] = 1.0 / face;}
for (int i=2; i<=n; i++) {
int subMaxPoint = i * face;
for (int j=i; j<=maxPoint; j++) {
for (int k=1; k<=face && k<=j; k++) {
dp[i][j] += dp[i-1][j-k] / face;
}
}
}
List<Map.Entry<Integer, Double>> res = new ArrayList<>();
for (int i=n; i<=maxPoint; i++) {
res.add(new AbstractMap.SimpleEntry<Integer, Double>(i, dp[n][i]));
}
return res;
}
}
2. 旋转数组O(N)
public class Solution {
/**
* @param n an integer
* @return a list of Map.Entry<sum, probability>
*/
public List<Map.Entry<Integer, Double>> dicesSum(int n) {
int face = 6;
int maxPoint = 6 * n;
int flag = 1;
double[][] dp = new double[2][maxPoint+1];
for (int i=1; i<=face; i++) {dp[0][i] = 1.0 / face;}
for (int i=2; i<=n; i++, flag=1-flag) {
int subMaxPoint = i * face;
for (int j=0; j<=maxPoint; j++) {
dp[flag][j] = 0;
}
for (int j=i; j<=subMaxPoint; j++) {
for (int k=1; k<=face && k<=j; k++) {
dp[flag][j] += dp[1-flag][j-k] / face;
}
}
}
List<Map.Entry<Integer, Double>> res = new ArrayList<>();
for (int i=n; i<=maxPoint; i++) {
res.add(new AbstractMap.SimpleEntry<Integer, Double>(i, dp[1-flag][i]));
}
return res;
}
}