2019.3.6 《剑指Offer》从零单刷个人笔记整理(66题全)目录传送门
这道书上的题牛客网没有,不知道是不是漏掉了。
求n个骰子所有可能的情况其实是固定的,一共次。因此求所有点数和s的概率实际上要的是求所有点数和s出现的次数。
由于每一粒骰子的情况都是固定的,因此多粒骰子的情况实际上就是每一种情况的不断累加的结果。
只有1粒骰子的时候,结果是分布是:1,2,3,4,5,6
当有2粒骰子的时候,把1-6分别加至1粒骰子的结果分布:
1 + 1,2,3,4,5,6 —— 2,3,4,5,6,7
2 + 1,2,3,4,5,6 —— 3,4,5,6,7,8
3 + 1,2,3,4,5,6 —— 4,5,6,7,8,9
4 + 1,2,3,4,5,6 —— 5,6,7,8,9,10
5 + 1,2,3,4,5,6 —— 6,7,8,9,10,11
6 + 1,2,3,4,5,6 —— 7,8,9,10,11,12
结果的分布是:2,3,4,5,6,7,8,9,10,11,12
很容易能够想到,当有n粒骰子时候,要把1-6分别加至n-1粒骰子的结果分布上。很明显的一种递归思想。
1. 创建一个6n - (n - 1)的结果数组result(6n为最大可能的结果,n - 1为排除掉的最小数字。result的可能确定,当分布不定)
2. 将n粒骰子分为a堆(1粒)和b堆(n-1粒),将a堆的骰子点数1-6加至b堆的所有点数和sum之中,骰子总数idx-1。
3. 当骰子加至最后一个,即idx为1时,骰子出现一种可能的情况,这种情况的结果为sum,对结果数组result[sum - number]中的值+1。
4. 递归进行1-3过程,程序运行会出现n层递归一种种可能的递归分支,其分布最终记录result数组中。
递归法存在n总数过大导致内存崩溃的问题,还可以逆向思考,转换一种思路。
将关注点着眼在结果数组result上,观察result数值的变化。
只有1粒骰子的时候,
结果分布:1,2,3,4,5,6
出现次数:1,1,1,1,1,1
当有2粒骰子的时候,
结果分布:2,3,4,5,6,7,8,9,10,11,12
出现次数:1,2,3,4,5,6,5,4, 3 , 2 , 1
result分布上的变化实际上是由新加入的2号骰子对1号骰子结果分布的不断更新。
1 —— 0
2 —— 1 + 1
3 —— 1 + 2,2 + 1
4 —— 1 + 3,2 + 2,3 + 1
5 —— 1 + 4,2 + 3,3 + 2,4 + 1
6 —— 1 + 5,2 + 4,3 + 3,4 + 2
7 —— 1 + 6,2 + 5,3 + 4,4 + 3,5 + 2,6 + 1
8 —— 2 + 6,3 + 5,4 + 4,5 + 3,6 + 2
9 —— 3 + 6,4 + 5,5 + 4,6 + 3
10 —— 4 + 6,5 + 5,6 + 4
11 —— 5 + 6,6 + 5
12 —— 6 + 6
经过观察可以发现,第i-1号骰子的result_old(min—max)的分布,会对第i号骰子结果result_new的分布(min+1—max+6)产生贡献。result_new中结果num的出现次数等于num-6至num-1所有出现次数的和,即
result_new[num] = result_old[num - 1] + result_old[num - 2] + result_old[num - 3] + result_old[num - 4] + result_old[num - 5] + result_old[num - 6]
因为前i-1个骰子所得的结果num-x(x=1-6)的结果加上第i号骰子得出的x,即可产生新的结果num。以此类推。
1. 创建一个6n的结果数组result(6n为最大可能的结果)
2. 记录第1粒骰子的初始result值,result[j](j = 1-6)均为1
3. 从第2粒骰子开始,对result进行更新。对于第i粒骰子,将result[j](j = i—6*i)中的结果进行更新,result[j]等于result[j-k](k=1-6)的和。
4. 将结果数组进行复制保存(或者将i之前的所有次数重置为0)
5. 循环进行步骤3-4
把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s所有可能值出现的概率。
Java实现:
/**
*
* @author ChopinXBP
* 把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s所有可能值出现的概率
*
*/
public class PrintProbability_66 {
public static void main(String[] args) {
// TODO Auto-generated method stub
int number = 6;
int[] result = PrintProbability(6);
int[] result2 = PrintProbability1(6);
for (int i = 0; i <= number * 6 - number; i++) {
System.out.println("i: " + i + " count: " + result[i]);
}
System.out.println(" ");
for (int i = number; i <= number * 6; i++) {
System.out.println("i: " + i + " count: " + result2[i]);
}
System.out.println(" ");
}
//方法一:递归法
public static int[] PrintProbability(int number) {
int[] result = new int[number * 6 - number + 1];
for (int i = 1; i <= 6; i++) {
probability(number, number, i, result);
}
return result;
}
//将骰子分为两堆,a堆有1个,b堆有idx-1个
public static void probability(int number, int idx, int sum, int[] result) {
//当骰子只剩一个时,将其点数和进行记录
if (idx == 1) {
result[sum - number]++;
}
else {
//将a堆的骰子点数1-6加至b堆的所有点数和。
for (int i = 1; i <= 6; i++) {
probability(number, idx - 1, sum + i, result);
}
}
}
//方法二:循环法
public static int[] PrintProbability1(int number) {
int[] result = new int[6 * number];
// 第一次抛掷骰子
for (int i = 1; i <= 6; i++) {
result[i] = 1;
}
for(int i = 2; i <= number; i++) {
int[] temp = new int[6 * number + 1];
// i骰子的出现次数j等于之前次数为j-k(k=1-6)的次数总和。k不能大于j
for(int j = i; j <= 6 * i; j++) {
for(int k = 1; k <= j && k <= 6; k++) {
temp[j] += result[j - k];
}
}
result = temp;
}
return result;
}
}
C++实现示例:
#include "stdafx.h"
#include
int g_maxValue = 6;
// ====================递归法====================
void Probability(int number, int* pProbabilities);
void Probability(int original, int current, int sum, int* pProbabilities);
void PrintProbability_Solution1(int number)
{
if(number < 1)
return;
int maxSum = number * g_maxValue;
int* pProbabilities = new int[maxSum - number + 1];
for(int i = number; i <= maxSum; ++i)
pProbabilities[i - number] = 0;
Probability(number, pProbabilities);
int total = pow((double)g_maxValue, number);
for(int i = number; i <= maxSum; ++i)
{
double ratio = (double)pProbabilities[i - number] / total;
printf("%d: %e\n", i, ratio);
}
delete[] pProbabilities;
}
void Probability(int number, int* pProbabilities)
{
for(int i = 1; i <= g_maxValue; ++i)
Probability(number, number, i, pProbabilities);
}
void Probability(int original, int current, int sum,
int* pProbabilities)
{
if(current == 1)
{
pProbabilities[sum - original]++;
}
else
{
for(int i = 1; i <= g_maxValue; ++i)
{
Probability(original, current - 1, i + sum, pProbabilities);
}
}
}
// ====================循环法====================
void PrintProbability_Solution2(int number)
{
if(number < 1)
return;
int* pProbabilities[2];
pProbabilities[0] = new int[g_maxValue * number + 1];
pProbabilities[1] = new int[g_maxValue * number + 1];
for(int i = 0; i < g_maxValue * number + 1; ++i)
{
pProbabilities[0][i] = 0;
pProbabilities[1][i] = 0;
}
int flag = 0;
for (int i = 1; i <= g_maxValue; ++i)
pProbabilities[flag][i] = 1;
for (int k = 2; k <= number; ++k)
{
for(int i = 0; i < k; ++i)
pProbabilities[1 - flag][i] = 0;
for (int i = k; i <= g_maxValue * k; ++i)
{
pProbabilities[1 - flag][i] = 0;
for(int j = 1; j <= i && j <= g_maxValue; ++j)
pProbabilities[1 - flag][i] += pProbabilities[flag][i - j];
}
flag = 1 - flag;
}
double total = pow((double)g_maxValue, number);
for(int i = number; i <= g_maxValue * number; ++i)
{
double ratio = (double)pProbabilities[flag][i] / total;
printf("%d: %e\n", i, ratio);
}
delete[] pProbabilities[0];
delete[] pProbabilities[1];
}
#Coding一小时,Copying一秒钟。留个言点个赞呗,谢谢你#