直接法
假设骰子有face面,有n个骰子,那么总排列数就有faceⁿ个。(例如,有3个6面骰子,那么其总排列数为6³=216个)。所有骰子的和最小值为n(假设骰子最小值为1),而和最大值为n * face(例如,有3个6面骰子,那么和最大值为18), 那么就有 (n * face - n + 1)个可能和值,那么新建长度为(n * face - n + 1)的一维数组进行统计不同S出现的次数。
然后骰子分别依次一个一个地投,并将其可能的值累加,最后将相应数组元素自增。
最后,遍历数组,除以总排列数,得出结果。
该算法时间复杂度为O(faceⁿ),当n越大,运算时间越长。(n从12开始增大,等待时间就开始难以接受)空间复杂度为O(n * face)。
动态规划
确定问题解的表达式。可将f(n, s)表示n个骰子点数的和为s的排列情况总数
确定状态转移方程。n个骰子点数和为s的种类数只与n-1个骰子的和有关。因为一个普通骰子有六个点数,那么第n个骰子可能出现1到6的点数。所以第n个骰子点数为1的话,f(n,s)=f(n-1,s-1),当第n个骰子点数为2的话,f(n,s)=f(n-1,s-2),…,依次类推。在n-1个骰子的基础上,再增加一个骰子出现点数和为s的结果只有这6种情况!那么有:
f(n,s) = f(n-1,s-1) + f(n-1,s-2) + f(n-1,s-3) + f(n-1,s-4) + f(n-1,s-5) + f(n-1,s-6)
上面就是状态转移方程,已知初始阶段的解为:当n=1时, f(1,1)=f(1,2)=f(1,3)=f(1,4)=f(1,5)=f(1,6)=1。
该算法时间复杂度为O(n²),空间复杂度为O(n²)。
以3个6面骰子为例,所用到dp[i][j]数组如下图所示。
| i\j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
| 3 | 0 | 0 | 0 | 1 | 3 | 6 | 10 | 15 | 21 | 25 | 27 | 27 | 25 | 21 | 15 | 10 | 6 | 3 | 1 |
一、直接法
import java.util.Arrays;
public class SumOfNDices {
public static int DICE_MAX_VALUE = 6;
public double[] getProbability(int numOfDice) {
if(numOfDice < 1) {
throw new IllegalArgumentException();
}
int maxSum = DICE_MAX_VALUE * numOfDice;
int[] sums = new int[maxSum - numOfDice + 1];
Arrays.fill(sums, 0);
setSums(numOfDice, sums);
int total = (int)Math.pow(DICE_MAX_VALUE, numOfDice);
return Arrays.stream(sums).mapToDouble((a)->(a * 1.0 / total)).toArray();
}
public void setSums(int numOfDice, int[] sums) {
for(int i = 1; i <= DICE_MAX_VALUE; i++) {
setSums(numOfDice, numOfDice - 1, i, sums);
}
}
public void setSums(int numOfDice, int leftNumOfDice, int sum, int[] sums) {
if(leftNumOfDice == 0) {
sums[sum - numOfDice]++;
}else {
for(int i = 1; i <= DICE_MAX_VALUE; i++) {
setSums(numOfDice, leftNumOfDice - 1, i + sum, sums);
}
}
}
}
二、动态规划(二维数组)
public double[] getProbability2(int numOfDice) {
if(numOfDice < 1) {
throw new IllegalArgumentException();
}
int[][] dp = new int[numOfDice + 1][numOfDice * DICE_MAX_VALUE + 1];
double[] result = new double[numOfDice * DICE_MAX_VALUE - numOfDice + 1];
double total = Math.pow(DICE_MAX_VALUE, numOfDice);
Arrays.fill(dp[1], 1, DICE_MAX_VALUE + 1, 1);
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
-nBqkecDZ-1710847735823)]
[外链图片转存中…(img-kw64c3n1-1710847735823)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-qRNBjaX6-1710847735824)]