解题笔记(18)――n个骰子的点数 - wuzhekai的专栏 - 博客频道 - CSDN.NET
问题描述:把n个骰子扔在地上,所有骰子朝上一面的点数之和为S。输入n,打印出S的所有可能的值出现的概率。
思路:这是一道应用动态规划思想的题目,而动态规划最难的就是要找最优子结构。并采取一种称为备忘录的方法避免重复计算。因为备忘录方法为每个解过的子问题建立了备忘录,以备需要时参看,避免了相同子问题的重复求解。
本题的最优子结构为:F(k, n) 表示k个骰子点数和为n的种数,k表示骰子个数,n表示k个骰子的点数和
/ = F(k-1, n-6) + F(k-1, n-5) + F(k-1, n-4) + F(k-1, n-3) + F(k-1, n-2) + F(k-1, n-1) 对于 k > 0, k <= n <= 6*k
F(k, n) =
\ = 0 对于 n < k or n > 6*k
当k=1时, F(1,1)=F(1,2)=F(1,3)=F(1,4)=F(1,5)=F(1,6)=1。
从上面公式可以看出,k个骰子点数和为n的种数只与k-1个骰子的和有关。这就可以用到备忘录的方法,用一张表格保存已解决的子问题的解,然后自底向上填表。考虑到当前层的计算只与下一层有关,因此只需保存一行。
[剑指offer] 面试题43:n个骰子的点数(Java)
XXX: DP
http://bylijinnan.iteye.com/blog/1428099
问题描述:把n个骰子扔在地上,所有骰子朝上一面的点数之和为S。输入n,打印出S的所有可能的值出现的概率。
思路:这是一道应用动态规划思想的题目,而动态规划最难的就是要找最优子结构。并采取一种称为备忘录的方法避免重复计算。因为备忘录方法为每个解过的子问题建立了备忘录,以备需要时参看,避免了相同子问题的重复求解。
本题的最优子结构为:F(k, n) 表示k个骰子点数和为n的种数,k表示骰子个数,n表示k个骰子的点数和
/ = F(k-1, n-6) + F(k-1, n-5) + F(k-1, n-4) + F(k-1, n-3) + F(k-1, n-2) + F(k-1, n-1) 对于 k > 0, k <= n <= 6*k
F(k, n) =
\ = 0 对于 n < k or n > 6*k
当k=1时, F(1,1)=F(1,2)=F(1,3)=F(1,4)=F(1,5)=F(1,6)=1。
从上面公式可以看出,k个骰子点数和为n的种数只与k-1个骰子的和有关。这就可以用到备忘录的方法,用一张表格保存已解决的子问题的解,然后自底向上填表。考虑到当前层的计算只与下一层有关,因此只需保存一行。
[剑指offer] 面试题43:n个骰子的点数(Java)
分析: 一般来说骰子只有6面,点数为1~6,故n个骰子的最小和为n,最大和为6*n,则n个骰子的点数之和出现的频数可以用一个数组来保存,大小为6*n-n。
思路1:最直接的方法是求出n个骰子点数的全排列,然后一一计算其和值,统计,最后算出每个和值的概率,这里使用基于递归的方法来求各和值的频数.先把n个骰子分为两堆:第一堆只有一个,另一个有n-1个。单独的那一个有可能出现从1到6的点数。我们需要计算从1到6的每一种点数和剩下的n-1个骰子来计算点数和。接下来把剩下的n-1个骰子还是分成两堆,第一堆只有一个,第二堆有n-2个。我们把上一轮那个单独骰子的点数和这一轮单独骰子的点数相加,再和剩下的n-2个骰子来计算点数和。
private
int
gMaxValue =
6
;
public
void
probility(
int
orignal,
int
current,
int
sum,
int
[] probilities){
if
(current ==
1
){
probilities[sum - orignal]++;
}
else
{
for
(
int
i=
1
; i<= gMaxValue; i++){
probility(orignal, current -
1
, sum + i, probilities);
}
}
}
public
void
probility(
int
number,
int
[] probilities){
for
(
int
i=
1
; i<= gMaxValue; i++){
probility(number,number, i,probilities);
}
}
public
void
printProbability(
int
number){
if
(number <
1
)
return
;
int
maxSum = number * gMaxValue;
int
[] probilities =
new
int
[maxSum - number +
1
];
probility(number, probilities);
double
total = Math.pow(gMaxValue, number);
for
(
int
i=number; i<= maxSum; i++){
double
ratio = probilities[i - number] / total;
System.out.println(i +
": "
+ ratio);
}
}
public
static
void
main(String[] args){
int
number =
5
;
new
PrintSumProbabilityOfDices().printProbability(number);
}
- public static void printProbabilityOfDice(int n){
- if(n<1){
- return;
- }
- double total=Math.pow(MAX, n);
- int len=n*MAX-n*1+1;//the sum of n dices is from n*1 to n*MAX
- int[] times=new int[len];
- for(int i=1;i<=MAX;i++){//initial the first dice.
- probabilityOfDice(n,i,n,0,times);//count the times of each possible sum
- }
- for(int i=0;i<len;i++){
- System.out.println((i+n)+","+times[i]+"/"+total);
- }
- }
- public static void probabilityOfDice(int n,int curDiceValue,int numOfDices,int curSum,int[] times){
- if(numOfDices==1){
- int sum=curSum+curDiceValue;
- times[sum-n]++;//n*1 to n*MAX <---> 0 to len
- }else{
- int sum=curSum+curDiceValue;
- for(int i=1;i<=MAX;i++){
- probabilityOfDice(n,i,numOfDices-1,sum,times);
- }
- }
- }
XX. 我们可以考虑用两个数组来存储骰子点数每一总数出现的次数。在一次循环中,第一个数组中的第n个数字表示骰子和为n出现的次数。那么在下一循环中,我们加上一个新的骰子。那么此时和为n的骰子出现的次数,应该等于上一次循环中骰子点数和为n-1、n-2、n-3、n-4、n-5与n-6的总和。所以我们把另一个数组的第n个数字设为前一个数组对应的第n-1、n-2、n-3、n-4、n-5与n-6之和。
上述代码没有在函数里把一个骰子的最大点数硬编码(hard co de)为6,而是用一个变量g_maxValue来表示。这样做的好处时,如果某个厂家生产了最大点数为4或者8的骰子,我们只需要在代码中修改一个地方,扩展起来很方便。如果在面试的时候我们能对面试官提起对程序扩展性的考虑,一定能给面试官留下一个很好的印象。
public
void
printProbability_2(
int
number){
if
(number <
1
)
return
;
int
maxSum = gMaxValue * number +
1
;
int
[][] probilities =
new
int
[
2
][maxSum];
int
flag =
0
;
for
(
int
i=
1
; i<=gMaxValue; i++)
probilities[flag][i] =
1
;
for
(
int
i=
2
; i<= number; i++){
for
(
int
j =
0
; j<i; j++)
probilities[
1
-flag][j] =
0
;
for
(
int
j = i; j<=gMaxValue * i; j++){
probilities[
1
-flag][j] =
0
;
for
(
int
k=
1
; k<=j && k<=gMaxValue; k++)
probilities[
1
-flag][j] += probilities[flag][j-k];
}
flag =
1
- flag;
}
double
total = Math.pow(gMaxValue, number);
for
(
int
i=number; i< maxSum; i++){
double
ratio = probilities[flag][i] / total;
System.out.println(i +
": "
+ ratio);
}
}
http://bylijinnan.iteye.com/blog/1428099
- /*
- 有k-1个骰子时,再增加一个骰子,这个骰子的点数只可能为1、2、3、4、5或6。那k个骰子得到点数和为n的情况有:
- (k-1,n-1):第k个骰子投了点数1
- (k-1,n-2):第k个骰子投了点数2
- (k-1,n-3):第k个骰子投了点数3
- ....
- (k-1,n-6):第k个骰子投了点数6
- 在k-1个骰子的基础上,再增加一个骰子出现点数和为n的结果只有这6种情况!
- 所以:f(k,n)=f(k-1,n-1)+f(k-1,n-2)+f(k-1,n-3)+f(k-1,n-4)+f(k-1,n-5)+f(k-1,n-6)
- 初始化:有1个骰子,f(1,1)=f(1,2)=f(1,3)=f(1,4)=f(1,5)=f(1,6)=1。
- */
- public static void printProbabilityOfDice2(int n){
- if(n<1){
- return;
- }
- double total=Math.pow(MAX, n);
- int maxSum=n*MAX;
- double[][] f=new double[n+1][n*MAX+1];
- for(int i=1;i<=MAX;i++){
- f[1][i]=1;
- }
- for(int k=2;k<=n;k++){
- for(int sum=n;sum<=maxSum;sum++){
- for(int i=1;sum-i>=1&&i<=MAX;i++){
- f[k][sum]+=f[k-1][sum-i];
- }
- }
- }
- for(int sum=n;sum<=maxSum;sum++){
- System.out.println(sum+","+f[n][sum]+"/"+total);
- }
- }