Lintcode - 20.骰子求和
题目:
扔 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.骰子求和的更多相关文章
- LintCode2016年8月22日算法比赛----骰子求和
骰子求和 题目描述 扔n个骰子,向上面的数字之和为 S .给定 Given n,请列出所有可能的 S 值及其相应的概率. 样例 给定n=1,返回 [ [1, 0.17], [2, 0.17], [3, ...
- java骰子求和算法
//扔 n 个骰子,向上面的数字之和为 S.给定 Given n,请列出所有可能的 S 值及其相应的概率public class Solution { /** * @param n an intege ...
- PAT甲题题解-1005. Spell It Right (20)-数位求和,水
把每个位上的数字求和sum,然后以英文单词的形式输出sum的每个位 #include <iostream> #include <cstdio> #include <alg ...
- 【LintCode】链表求和
问题分析: 我们通过遍历两个链表拿到每个位的值,两个值加上前一位进位值(0或者1)模10就是该位的值,除以10就是向高位的进位值(0或者1). 由于两个链表可以不一样长,所以要及时判断,一旦为null ...
- [03]java中的方法以及控制语句
00 Java中的语句块 语句块(有时叫做复合语句),是用花括号扩起的任意数量的简单Java语句.块确定了局部变量的作用域.块中的程序代码,作为一个整体,是要被一起执行的.块可以被嵌套在另一个块中,但 ...
- Java 线程池
系统启动一个线程的成本是比较高的,因为它涉及到与操作系统的交互,使用线程池的好处是提高性能,当系统中包含大量并发的线程时,会导致系统性能剧烈下降,甚至导致JVM崩溃,而线程池的最大线程数参数可以控制系 ...
- Java中必须了解的常用类
1.Java的包装类 基本数据类型我们都很熟悉,例如:int.float.double.boolean.char等,基本数据类型不具备对象的特征,不能调用方法,一般能实现的功能比较简单,为了让基本数据 ...
- Java基础(36):String与基本数据类型之间的双向转换(Wrapper类)
Java 中基本类型和字符串之间的转换 在程序开发中,我们经常需要在基本数据类型和字符串之间进行转换. 其中,基本类型转换为字符串有三种方法: 1. 使用包装类的 toString() 方法 2. 使 ...
- C语言基础(转载自大海笔记)
# C语言基础2015年03月26日10:04:411. 语言排行榜C——java——objective-C2. 进制:进制:进位机制.用普通的话讲,应该为人为的定义一种度量来标识一样东西 ...
随机推荐
- WMware Vsphere取消某虚机的漂移
由于一些业务特性,有一些虚机不应该完全受集群DSR控制.下面记录一下如何更改某一虚机的漂移属性. 1.环境 VMware Vsphere web client 6.5 2.点击需要配置虚机所在的集群, ...
- 【缓存算法】FIFO,LFU,LRU
一.FIFO算法 FIFO(First in First out),先进先出.其实在操作系统的设计理念中很多地方都利用到了先进先出的思想,比如作业调度(先来先服务),为什么这个原则在很多地方都会用到呢 ...
- linux后台执行命令:&与nohup的用法
& 这种方法很简单,就是在命令之后加个“&”符号就可以了,如下: ./test & 这样一来,test程序就在后台运行了.但是,这样处理还不够,因为这样做虽然程序是在后台运行了 ...
- [IDEA]IDEA设置注释模板
IDEA的注释模板有类注释模板和方法注释模板两种,下面分别介绍: 一.类注释模板 菜单路径:File->Settings->Editor->File and Code Templat ...
- LOJ 2547 「JSOI2018」防御网络——思路+环DP
题目:https://loj.ac/problem/2547 一条树边 cr->v 会被计算 ( n-siz[v] ) * siz[v] 次.一条环边会被计算几次呢?于是去写了斯坦纳树. #in ...
- py-day4-3 python 内置函数 man和mix的高级使用
### man和mix的高级使用 age_dic={'xiaoma':18,'zhangfei':20,'xiaowang':36,'lihao':13,} print(max(age_dic.val ...
- C# Json 和对象的相互转换
Json 和对象的相互转换 /// <summary> /// Json 和对象的相互转换 ——需引用using Newtonsoft.Json; 即:Jsonnet.net..... / ...
- 工控随笔_20_西门子_WinCC的VBS脚本_09_常量和流程控制_02
vbs不但提供了分支结构,还提供了丰富的循环形式.一共有3种循环: 1.for循环 2.do...loop循环 3.while循环 各种循环有各自的特点,在使用的时候可以进行转换. 前面已经描述过Fo ...
- Lubuntu下小巧好用的Python编辑工具Geany
在Lubuntu系统上有很多的Python编辑器,如eclipse,PyCharm等,功能繁多,市场占有率高,但动辄几百M的体积有些巨大,博主今天推荐一款精简且易用的Python开发工具--Geany ...
- 图解python中赋值、浅拷贝、深拷贝的区别
Python中,对象的赋值,拷贝(深/浅拷贝)之间是有差异的,如果使用的时候不注意,就可能产生意外的结果.下面本文就通过简单的例子介绍一下这些概念之间的差别. 对象赋值 直接看一段代码: will = ...