描述
扔 n 个骰子,向上面的数字之和为 S。给定 Given n,请列出所有可能的 S 值及其相应的概率。
样例
给定 n = 1
,返回 [ [1, 0.17], [2, 0.17], [3, 0.17], [4, 0.17], [5, 0.17], [6, 0.17]]
。
这是剑指offer的一道题,主要思想为动态规划,由于投n个骰子出现的点数为n~6n,一共为6n-n+1=5n+1个数,所以我们需要申请一个长为5n+1的辅助数组a。数字的下标[i]表示出现的和(0表示n个骰子出现的最小的和,即n),下标对应的值a[i]表示对应的和出现的次数。
从最简单的开始,假设只有一个骰子,需要长度为6的辅助数组,每个骰子出现的次数为1,于是最后的数组a的长度为6,对应的值为[1,1,1,1,1,1]。假如有两个骰子,我们把骰子的和分为前n-1个骰子的和和第n个骰子的值的相加和,前n-1即2-1=1,即前1个的骰子的和的数组为[1,1,1,1,1,1],由于两个骰子的和的范围为2~12,以和为10为例,10可以分为前n-1组的值+第n个骰子的值,则:
10=1+9=2+8=3+7=......=9+1
由于第二个骰子出现的值为1~6,所以10只可能由以下的值组成4+6=5+5=6+4=7+3=8+2=9+1,所以和为10出现的次数等于和为4,5,6,7,8,9的次数相加而得,即f(n)=f(n-1)+f(n-2)+f(n-3)+f(n-4)+f(n-5)+f(n-6),所以这就是递归规律,这里需要注意,我们需要一个新的临时数组tmp来保存生成的值,最后再把tmp的值重新赋值给a。所以程序的思想如下,如果n=1,则通过递推公式构造数组a的0~5(下标)的数,如果n=2,则先构造0~5,再通过0~5构造0~10的数(因为2个骰子出现的和为2~12,归一到下标为0即0~10),往后以此类推。最后需要注意的一点是,由于在递归过程中数组的值(出现的次数)是指数次的增加,所以辅助数组需定义为long long,否则如果定义为int则会溢出(int最大为2147483647),以下为代码(c++,在lintcode上已ac)
//最后的答案返回格式为vector<pair<int, double>>
vector<pair<int, double>> dicesSum(int n) {
vector<pair<int, double>> result;
//辅助数组a
long long *array = new long long[5 * n + 1];
memset(array, 0, sizeof(long long)*(5 * n + 1));
dicesSumCore(n, array, result);
delete[] array;
return result;
}
//构造递推数组
void calculate(long long* array, int i, int n) {
//临时标量tmp,作用上文已说过
long long tmp[10001];
memset(tmp, 0, sizeof(long long)*(10001));
//sum表示第i项~第(i-6)项的和
long long sum = 0;
for (int j = 0; j <= 5 * (i + 1); j++) {
//前6项一直累加
if (j <= 5) {
sum += (array)[j];
tmp[j] = sum;
}
else {
//后面的需要减去第i-6项的值
sum += (array)[j] - (array)[j - 6];
tmp[j] = sum;
}
}
//最后把tmp的值赋给a
for (int j = 0; j <= 5 * (i + 1); j++) {
array[j] = tmp[j];
}
}
void dicesSumCore(int n, long long* &array, vector<pair<int, double>> & result) {
//初始化前6个值为1
for (int i = 0; i < 6; i++) {
array[i] = 1;
}
//如果n==1,则初始化后直接计算并赋值到result中返回
if (n == 1) {
for (int i = 0; i < 6; i++) {
pair<int, double> zy;
zy.first = i + 1;
zy.second = array[i] / pow(6, n);
result.push_back(zy);
}
return;
}
//i表示第几次递推,每次递推都构造一个n-6n的数组,注意i的下标从0开始,所以i=1表示n=2的第二次构造,构造的数组下标为0-5n(包含5n)
for (int i = 1; i < n; i++) {
//构造第i+1次递推数组
calculate(array, i, n);
//最后一次经过calculate构造后赋值到result中
if (i == n - 1) {
for (int i = 0; i <= 5 * n; i++) {
pair<int, double> zy;
zy.first = i + n;
zy.second = array[i] / pow(6, n);
result.push_back(zy);
}
}
}
}
递归解法:
/****************************
func:获取n个骰子指定点数和出现的次数
para:n:骰子个数;sum:指定的点数和
return:点数和为sum的排列数
****************************/
class Solution {
public:
int dicesSumCore(int n,int sum){
if(n<1 || sum<n || sum>6*n) return 0;
if(n==1) return 1;
return (dicesSumCore(n-1,sum-6)+dicesSumCore(n-1,sum-5)
+dicesSumCore(n-1,sum-4)+dicesSumCore(n-1,sum-3)
+dicesSumCore(n-1,sum-2)+dicesSumCore(n-1,sum-1));
}
/**
* @param n an integer
* @return a list of pair<sum, probability>
*/
vector<pair<int, double>> dicesSum(int n) {
// Write your code here
vector<pair<int, double>> res;
double sum_count=pow(6,n);
for(int i=n;i<=6*n;i++){
res.push_back({i,(dicesSumCore(n,i)/sum_count)});
}
return res;
}
};
非递归解法(注意归零操作和long long int操作):
class Solution {
public:
void dicesSumCore(int n,vector<long long int> &sum){
sum[0]=0;
sum[1]=sum[2]=sum[3]=sum[4]=sum[5]=sum[6]=1;
if(n==1) return;
for(int i=2;i<=n;i++){
//把相隔两个单位以上的清零,比如当i=3时,只考虑i=2时产生的2~12,所以必须把sum[0]=sum[1]=0清零
for (int t = 0; t < i-1; t++) sum[t] = 0;
for(int j=6*i;j>=i;j--){
long long int tmp1=((j-1)>=0?sum[j-1]:0);
long long int tmp2=((j-2)>=0?sum[j-2]:0);
long long int tmp3=((j-3)>=0?sum[j-3]:0);
long long int tmp4=((j-4)>=0?sum[j-4]:0);
long long int tmp5=((j-5)>=0?sum[j-5]:0);
long long int tmp6=((j-6)>=0?sum[j-6]:0);
sum[j]=tmp1+tmp2+tmp3+tmp4+tmp5+tmp6;
}
}
}
/**
* @param n an integer
* @return a list of pair<sum, probability>
*/
vector<pair<int, double>> dicesSum(int n) {
// Write your code here
vector<pair<int, double>> res;
vector<long long int> sum(6*n+1,0);
dicesSumCore(n,sum);
double sum_count=pow(6,n);
for(int i=n;i<=6*n;i++){
res.push_back({i,(sum[i]/sum_count)});
}
return res;
}
};