题目:

扔 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]]

方法一

概率问题最简单有效的方法当然是枚举啊,况且现在的计算机这么优秀。

n个骰子一起投掷,先列出所有可能的结果,然后求和,计数,最后算概率;

首先枚举需要的空间先给上

int[][] matrix = new int[(int) Math.pow(6, n)][n];

然后进行n重循环,枚举所有的可能情况

int speed = 0; // 变化的步长
int count = 0; // 计数器
int point = 0; // 当前要写入的数值 for (int i = 0; i < n; i++) {
speed = (int) Math.pow(6, i);
count = 0;
point = 0;
for (int j = 0; j < MAX; j++, count++) {
if (count == speed) {
count = 0;
point++;
}
matrix[j][i] = (int) (point % 6 + 1);
}
}

然后就获得了所有的情况,而且这些情况都是等概率的;

之后就就很容易了,

然而。。。

运行到 n = 8 的时候崩了。。。

把 int变成 short,再改成 char,都不好使,,,额,这个出题人不想让我们用枚举。。。

是的后面当 n = 15 时,本地的IDE页崩了;

Exception in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit

所以这题要用到一些算法知识。。。

方法二

再此向大家介绍全概公式:

全概率公式为概率论中的重要公式,它将对一复杂事件A的概率求解问题转化为了在不同情况下发生的简单事件的概率的求和问题。
内容:如果事件B1、B2、B3…Bn 构成一个完备事件组,即它们两两互不相容,其和为全集;并且P(Bi)大于0,则对任一事件A有
P(A)=P(A|B1)P(B1) + P(A|B2)P(B2) + ... + P(A|Bn)P(Bn)。
或者:p(A)=P(AB1)+P(AB2)+...+P(ABn)),其中A与Bn的关系为交)。

这个题目正好就是这样

投 6个骰子点数和为 25的概率 = 投 6个骰子点数和为 25并且最后一个点数为1的概率 + 投 6个骰子点数和为 25并且最后一个点数为2的概率

+ 投 6个骰子点数和为 25并且最后一个点数为3的概率 + ... + 投 6个骰子点数和为 25并且最后一个点数为6的概率;

换言之:

投 6个骰子点数和为 25的概率 = 投 前5个骰子点数和为 24并且最后一个点数为1的概率 + 投 前5个骰子点数和为 23并且最后一个点数为2的概率

+ 投 前5个骰子点数和为 22并且最后一个点数为3的概率 + ... + 投 前5个骰子点数和为 19并且最后一个点数为6的概率;

得出普遍结论:

投 n个骰子点数和为 Sum的概率 = 投 前 n-1个骰子点数和为 Sum-1并且最后一个点数为1的概率 + 投 前 n-1个骰子点数和为 Sum-2并且最后一个点数为2的概率

+ 投 前 n-1个骰子点数和为 Sum-3并且最后一个点数为3的概率 + ... + 投 前 n-1个骰子点数和为 Sum-6并且最后一个点数为6的概率;

举例检验:

投 2个骰子点数和为 6的概率 = 投 第一个骰子点数为 5并且第二个点数为1的概率(1/6 * 1/6) + 投 第一个骰子点数为 4并且第二个点数为2的概率(1/6 * 1/6)

+ 投 第一个骰子点数为 3并且第二个点数为3的概率(1/6 * 1/6) + 投 第一个骰子点数为 2并且第二个点数为4的概率(1/6 * 1/6)

+ 投 第一个骰子点数为 1并且第二个点数为5的概率(1/6 * 1/6) + 投 第一个骰子点数为 0并且第二个点数为6的概率(0/6 * 1/6) = 5/36;

投 2次的概率可以从投 1次的概率中得出,投 3次的概率可以从投 2次的概率中得出,投 4次的概率可以从投 3次的概率中得出...

所以我们可以从第二次一直计算到第 n次,

由于概率的分母为所有可能出现的情况的总数为定值:为 6的n次方;

所以我们可以先只记录可能种类的次数,最后再算概率;

double[][] matrix_II = new double[100][1000];

for (int i = 1; i < matrix_II.length; i++) {
for (int j = 0; j < matrix_II[0].length; j++) {
matrix_II[i][j] = 0;
}
} matrix_II[1][1] = matrix_II[1][2] = matrix_II[1][3] = 1;
matrix_II[1][4] = matrix_II[1][5] = matrix_II[1][6] = 1; for (int i = 2; i <= n; i++) {
for (int j = i; j <= 6 * i; j++) {
double sum2 = 0;
for (int k = j - 6; k <= j - 1; k++) {
if (k > 0) {
sum2 += matrix_II[i - 1][k];
}
}
matrix_II[i][j] = sum2;
}
}

格式化后,

第一层循环:从第二次到第 n次;

第二层循环:n次投掷可能的结果:n到 6n;

第三层循环:本次的概率与上次的 6种情形的概率有关;

循环完毕即得到了一个从1到 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) {
// Write your code here
// Ps. new AbstractMap.SimpleEntry<Integer, Double>(sum, pro)
// to create the pair
double MAX = Math.pow(6, n); double[][] matrix_II = new double[100][1000]; for (int i = 1; i < matrix_II.length; i++) {
for (int j = 0; j < matrix_II[0].length; j++) {
matrix_II[i][j] = 0;
}
} matrix_II[1][1] = matrix_II[1][2] = matrix_II[1][3] = 1;
matrix_II[1][4] = matrix_II[1][5] = matrix_II[1][6] = 1; double sum2 = 0;
for (int i = 2; i <= n; i++) {
for (int j = i; j <= 6 * i; j++) {
sum2 = 0;
for (int k = j - 6; k <= j - 1; k++) {
if (k >= i - 1) {
sum2 += matrix_II[i - 1][k];
}
}
matrix_II[i][j] = sum2;
}
} List<Map.Entry<Integer, Double>> list = new ArrayList<>();
for (int i = n; i <= 6 * n; i++) {
AbstractMap.SimpleEntry<Integer, Double> entry = new AbstractMap.SimpleEntry<>(i, matrix_II[n][i] / MAX);
list.add(entry);
}
return list;
}
}

需要注意的是当程序运行到 n=15 时,数值已经大过 Int类型了;

哟。

进阶 方法三

上面的方法二应该是最有效的方法了,但似乎感觉还是有点不让人满意,求 n的概率必须把前面的概率都算一遍;

有没有一种直接就去找 n的算法呢?

有啊,因为我们平时的思考方式肯定不是从 1开始推啊;

常见的问题:投掷 3个骰子向上的点数和为 7的概率为多少?

肯定是推:有115(3),124(6),133(3),223(3),3+6+3+3=15,概率为15/36 = 5/12;

我们也可以这样啊,分别直接去求 n到 6n的概率;

1.获取去重全排列的个数;

2.获取关键的数组(如115,124等);

首先是简单的获取第一个关键数组,即和为 sum,各个位置的数值从左往右递增;

int[] arr = new int[n];
for (int i = 0; i < n - 1; i++) {
arr[i] = 1;
}
arr[n - 1] = sum - n + 1; for (int i = 1; i < n; i++) {
if (arr[n - i] > 6) {
arr[n - i - 1] = arr[n - i - 1] + arr[n - i] - 6;
arr[n - i] = 6;
}
}

从倒数第二位开始,进行判断,替换和重新构造;

哈哈,之后就不会写了。。。

for (int i = n - 2; i >= 0; i--) { // 从倒数第二个开始
while (arr[i] + 1 < 6 && arr[i + 1] - 1 > 0 && arr[i + 1] - arr[i] >= 2) {
arr[i] += 1;
arr[i + 1] -= 1;
for (int index = 0; index < n; index++) {
System.out.print(arr[index]);
}
System.out.println();
}
}

大致是这么个结构。。。但还少进位,重构等功能;

我在这里就阵亡了,诸位算法大师,数学家,就拜托你们了;

重置 方法一

谁说枚举不能用了。。。只要不占用那么多的空间不就好了;

上程序

public class Solution {
/**
* @param n an integer
* @return a list of Map.Entry<sum, probability>
*/
public List<Map.Entry<Integer, Double>> dicesSum(int n) {
// Write your code here
// Ps. new AbstractMap.SimpleEntry<Integer, Double>(sum, pro)
// to create the pair
double MAX = Math.pow(6, n); double[] sum_array = new double[6 * n + 1];
for (int i = 0; i < sum_array.length; i++) {
sum_array[i] = 0;
} int[]matrix = new int[n+1];
for (int i = 1; i < matrix.length; i++) {
matrix[i] = 1;
} int sum;
while (true) { sum = 0;
for (int b = 1; b <= n; b++) {
sum += matrix[b];
}
sum_array[sum]++; matrix[n]++;
for (int i = n; i > 0; i--) {
if(matrix[i] == 7) {
matrix[i-1]++;
matrix[i] = 1;
}
}
if(matrix[0] > 0) {
break;
}
} List<Map.Entry<Integer, Double>> list = new ArrayList<>();
for (int i = n; i <= 6 * n; i++) {
AbstractMap.SimpleEntry<Integer, Double> entry = new AbstractMap.SimpleEntry<>(i, sum_array[i] / MAX);
list.add(entry);
}
return list;
}
}

就想到了。。。枚举超时了。。。

行了,不挣扎了。

Lintcode - 20.骰子求和的更多相关文章

  1. LintCode2016年8月22日算法比赛----骰子求和

    骰子求和 题目描述 扔n个骰子,向上面的数字之和为 S .给定 Given n,请列出所有可能的 S 值及其相应的概率. 样例 给定n=1,返回 [ [1, 0.17], [2, 0.17], [3, ...

  2. java骰子求和算法

    //扔 n 个骰子,向上面的数字之和为 S.给定 Given n,请列出所有可能的 S 值及其相应的概率public class Solution { /** * @param n an intege ...

  3. PAT甲题题解-1005. Spell It Right (20)-数位求和,水

    把每个位上的数字求和sum,然后以英文单词的形式输出sum的每个位 #include <iostream> #include <cstdio> #include <alg ...

  4. 【LintCode】链表求和

    问题分析: 我们通过遍历两个链表拿到每个位的值,两个值加上前一位进位值(0或者1)模10就是该位的值,除以10就是向高位的进位值(0或者1). 由于两个链表可以不一样长,所以要及时判断,一旦为null ...

  5. [03]java中的方法以及控制语句

    00 Java中的语句块 语句块(有时叫做复合语句),是用花括号扩起的任意数量的简单Java语句.块确定了局部变量的作用域.块中的程序代码,作为一个整体,是要被一起执行的.块可以被嵌套在另一个块中,但 ...

  6. Java 线程池

    系统启动一个线程的成本是比较高的,因为它涉及到与操作系统的交互,使用线程池的好处是提高性能,当系统中包含大量并发的线程时,会导致系统性能剧烈下降,甚至导致JVM崩溃,而线程池的最大线程数参数可以控制系 ...

  7. Java中必须了解的常用类

    1.Java的包装类 基本数据类型我们都很熟悉,例如:int.float.double.boolean.char等,基本数据类型不具备对象的特征,不能调用方法,一般能实现的功能比较简单,为了让基本数据 ...

  8. Java基础(36):String与基本数据类型之间的双向转换(Wrapper类)

    Java 中基本类型和字符串之间的转换 在程序开发中,我们经常需要在基本数据类型和字符串之间进行转换. 其中,基本类型转换为字符串有三种方法: 1. 使用包装类的 toString() 方法 2. 使 ...

  9. C语言基础(转载自大海笔记)

    # C语言基础2015年03月26日10:04:411.    语言排行榜C——java——objective-C2.    进制:进制:进位机制.用普通的话讲,应该为人为的定义一种度量来标识一样东西 ...

随机推荐

  1. npm -D -S 的区别

    npm -D -S 的区别   我们在使用npm install 安装模块或插件的时候,有两种命令把他们写入到 package.json 文件里面去,比如: --save-dev --save 在 p ...

  2. 获取【请求体】数据的3种方式(精)(文末代码) request.getInputStream() request.getInputStream() request.getReader()

    application/x- www-form-urlencoded是Post请求默认的请求体内容类型,也是form表单默认的类型.Servlet API规范中对该类型的请求内容提供了request. ...

  3. golang-http-post

    func httpPost() { resp, err := http.Post("https://www.abcd123.top/api/v1/login", "app ...

  4. form表单图片上传

    1.前端页面 <div class="tkDiv" id="addLOGO" style="display:none;z-index:12;wi ...

  5. DNS子域授权

    DNS子域授权 当一个域很大时,而且还有上,下层关系,如果所有的记录变更都由某一台服务器来管理的话,那将会是什么样子?就好比一个公司的总经理直接管理公司1000个人的所有事项,恐怕会被累死.所以会在总 ...

  6. C#用反射实现两个类的对象之间相同属性的值的复制

    在进行实体转换操作的时候如果需要在对两个实体之间两个属性字段差不多相同的类要进行一个互相的转换,我们要把a对象的所有字段的值都复制给b对象,我们只能用b.属性=a.属性来写,如果属性字段太多的话,就要 ...

  7. nginx 配置说明

    ======nginx 配置文件分开==== http://blog.csdn.net/baple/article/details/44197981 1.备份现在有nginx.conf2.复制ngin ...

  8. 【Angular】——TypeScript之胖箭头(=>)函数

    前言:胖箭头(=>)函数是一种快速书写函数的简介语法. ES5和TypeScript比较:在ES5中,每当我们要用甘薯作为方法参数时,都必须用function关键字和紧随其后的花括号({})表示 ...

  9. Linux环境下使用Android NDK编译c/c++生成可执行文件

    1.安装Android NDK至Linux(Lubuntu 16) 从网上下载 android-ndk-r13b-linux-x86_64.zip,本人将其解压至/home/guanglun/work ...

  10. vue插件大全汇总

    Vue是一个构建数据驱动的 web 界面的渐进式框架.Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件特别整理了常用的vue插件,来了个大汇总,方便查找使用,便于工作 ...