题目:

扔 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个骰子一起投掷,先列出所有可能的结果,然后求和,计数,最后算概率;

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

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

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

  1. int speed = 0; // 变化的步长
  2. int count = 0; // 计数器
  3. int point = 0; // 当前要写入的数值
  4.  
  5. for (int i = 0; i < n; i++) {
  6. speed = (int) Math.pow(6, i);
  7. count = 0;
  8. point = 0;
  9. for (int j = 0; j < MAX; j++, count++) {
  10. if (count == speed) {
  11. count = 0;
  12. point++;
  13. }
  14. matrix[j][i] = (int) (point % 6 + 1);
  15. }
  16. }

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

之后就就很容易了,

然而。。。

运行到 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次方;

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

  1. double[][] matrix_II = new double[100][1000];
  2.  
  3. for (int i = 1; i < matrix_II.length; i++) {
  4. for (int j = 0; j < matrix_II[0].length; j++) {
  5. matrix_II[i][j] = 0;
  6. }
  7. }
  8.  
  9. matrix_II[1][1] = matrix_II[1][2] = matrix_II[1][3] = 1;
  10. matrix_II[1][4] = matrix_II[1][5] = matrix_II[1][6] = 1;
  11.  
  12. for (int i = 2; i <= n; i++) {
  13. for (int j = i; j <= 6 * i; j++) {
  14. double sum2 = 0;
  15. for (int k = j - 6; k <= j - 1; k++) {
  16. if (k > 0) {
  17. sum2 += matrix_II[i - 1][k];
  18. }
  19. }
  20. matrix_II[i][j] = sum2;
  21. }
  22. }

格式化后,

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

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

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

循环完毕即得到了一个从1到 n次的投掷情况的次数矩阵;

然后只要在最后遍历一次最后一趟作为输出就行了;

附上程序:

  1. public class Solution {
  2. /**
  3. * @param n an integer
  4. * @return a list of Map.Entry<sum, probability>
  5. */
  6. public List<Map.Entry<Integer, Double>> dicesSum(int n) {
  7. // Write your code here
  8. // Ps. new AbstractMap.SimpleEntry<Integer, Double>(sum, pro)
  9. // to create the pair
  10. double MAX = Math.pow(6, n);
  11.  
  12. double[][] matrix_II = new double[100][1000];
  13.  
  14. for (int i = 1; i < matrix_II.length; i++) {
  15. for (int j = 0; j < matrix_II[0].length; j++) {
  16. matrix_II[i][j] = 0;
  17. }
  18. }
  19.  
  20. matrix_II[1][1] = matrix_II[1][2] = matrix_II[1][3] = 1;
  21. matrix_II[1][4] = matrix_II[1][5] = matrix_II[1][6] = 1;
  22.  
  23. double sum2 = 0;
  24. for (int i = 2; i <= n; i++) {
  25. for (int j = i; j <= 6 * i; j++) {
  26. sum2 = 0;
  27. for (int k = j - 6; k <= j - 1; k++) {
  28. if (k >= i - 1) {
  29. sum2 += matrix_II[i - 1][k];
  30. }
  31. }
  32. matrix_II[i][j] = sum2;
  33. }
  34. }
  35.  
  36. List<Map.Entry<Integer, Double>> list = new ArrayList<>();
  37. for (int i = n; i <= 6 * n; i++) {
  38. AbstractMap.SimpleEntry<Integer, Double> entry = new AbstractMap.SimpleEntry<>(i, matrix_II[n][i] / MAX);
  39. list.add(entry);
  40. }
  41. return list;
  42. }
  43. }

需要注意的是当程序运行到 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,各个位置的数值从左往右递增;

  1. int[] arr = new int[n];
  2. for (int i = 0; i < n - 1; i++) {
  3. arr[i] = 1;
  4. }
  5. arr[n - 1] = sum - n + 1;
  6.  
  7. for (int i = 1; i < n; i++) {
  8. if (arr[n - i] > 6) {
  9. arr[n - i - 1] = arr[n - i - 1] + arr[n - i] - 6;
  10. arr[n - i] = 6;
  11. }
  12. }

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

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

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

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

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

重置 方法一

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

上程序

  1. public class Solution {
  2. /**
  3. * @param n an integer
  4. * @return a list of Map.Entry<sum, probability>
  5. */
  6. public List<Map.Entry<Integer, Double>> dicesSum(int n) {
  7. // Write your code here
  8. // Ps. new AbstractMap.SimpleEntry<Integer, Double>(sum, pro)
  9. // to create the pair
  10. double MAX = Math.pow(6, n);
  11.  
  12. double[] sum_array = new double[6 * n + 1];
  13. for (int i = 0; i < sum_array.length; i++) {
  14. sum_array[i] = 0;
  15. }
  16.  
  17. int[]matrix = new int[n+1];
  18. for (int i = 1; i < matrix.length; i++) {
  19. matrix[i] = 1;
  20. }
  21.  
  22. int sum;
  23. while (true) {
  24.  
  25. sum = 0;
  26. for (int b = 1; b <= n; b++) {
  27. sum += matrix[b];
  28. }
  29. sum_array[sum]++;
  30.  
  31. matrix[n]++;
  32. for (int i = n; i > 0; i--) {
  33. if(matrix[i] == 7) {
  34. matrix[i-1]++;
  35. matrix[i] = 1;
  36. }
  37. }
  38. if(matrix[0] > 0) {
  39. break;
  40. }
  41. }
  42.  
  43. List<Map.Entry<Integer, Double>> list = new ArrayList<>();
  44. for (int i = n; i <= 6 * n; i++) {
  45. AbstractMap.SimpleEntry<Integer, Double> entry = new AbstractMap.SimpleEntry<>(i, sum_array[i] / MAX);
  46. list.add(entry);
  47. }
  48. return list;
  49. }
  50. }

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

行了,不挣扎了。

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. Web 安全之 XSS 攻击与防御

    前言 黑客,相信大家对这一名词并不陌生,黑客们往往会利用 Web 应用程序的漏洞来攻击咱们的系统.开放式 Web 应用程序安全项目(OWASP, Open Web Application Securi ...

  2. 《大型网站系统与Java中间件实现》有感

    头一次只用了一周的时间就看完一本书<大型网站系统与Java中间件实现>,这本书是关于设计方面的,提到了服务框架,消息中间件,数据访问层,以及如何解决应用之间的调用,解耦,以及应用和存储之间 ...

  3. EBS查询在线用户

    转自:https://www.cnblogs.com/benio/archive/2011/03/10/1979417.html SELECT u.user_name, app.application ...

  4. TensorFlow机器学习实战指南之第一章

    TensorFlow基础 一.TensorFlow算法的一般流程 1.导入/生成样本数据集 2.转换和归一化数据:一般来讲,输入样本数据集并不符合TensorFlow期望的形状,所以需要转换数据格式以 ...

  5. 前端基础:web语义化

    web语义化 一.什么是web语义化? web语义化包含两方面,一是html标签语义化,简单来说就是要用合适的标签来表述适当的内容,标题用<h1>~~<h6>标签,段落用< ...

  6. Javascript 使用postMessage对iframe跨域传值或通信

    实现目标:两个网站页面实现跨域相互通信 当前例子依赖于 jQuery 3.0 父页面代码:www.a.com/a.html <iframe id="myIframe" src ...

  7. Mediawiki PlantUML Graphviz 图片 中文 乱码

    安装Mediawiki 的  PlantUML  Graphviz   插件后,生成图片时,中文成乱码问题. 环境:Ubuntu 16.04 MediaWiki 1.31.1 PHP 7.0.32-0 ...

  8. winmount导致资源管理器崩溃

    今天,在别人电脑上遇到一个问题,右键点击某个特定文件夹会导致资源管理器崩溃重启,提示为: Runtime Error! Program: C:\Windows\Explorer.exe This ap ...

  9. MySQL误操作删除后,怎么恢复数据?

    MySQL误操作删除后,怎么恢复数据?登陆查数据库mysql> select * from abc.stad;+----+-----------+| id | name |+----+----- ...

  10. CentOS7用hostapd做radius服务器为WiFi提供802.1X企业认证

    CentOS7用hostapd做radius服务器为WiFi提供802.1X企业认证 来源: https://www.cnblogs.com/osnosn/p/10593297.html 来自osno ...