前言
本博文部分图片, 思路来自于剑指offer 或者编程珠玑
问题描述
思路
对于这个问题, 书中给出了两种思路
这里 我代码中求解的是给定k表示k个骰子, n表示各个向上的点子和为n, 求解k个骰子投掷出n的概率
思路一 : 穷举..
思路二 : 首先我们已知的条件为1个骰子掷出1, 2, 3, 4, 5, 6的可能性为1种, 然后我们递推 其余k个骰子掷出的2, 3, 4, …的和为(i, i*6)的可能情况的个数
这其实 就是一种自底向上的动态规划[当然 也可以替换为带缓存的自顶向下]
对于2个骰子来说, 投掷出3的可能性为, 一个骰子投掷出1的可能情况个数 * 另外的(2-i)个骰子投出(3-1)的可能情况个数 + 一个骰子投出2的可能情况个数 * 另外的(2-i)个骰子投出(3-2)的可能情况个数 = 1 * 1 + 1 * 1 = 2, 所以 两个骰子掷出3的可能情况有两种, 这样就可以以递推到k个骰子了
参考代码
/**
* file name : Test18DiceProbability.java
* created at : 10:53:30 AM Jun 9, 2015
* created by 970655147
*/
package com.hx.test05;
public class Test18DiceProbability {
static final int DICE_FACE_NUM = 6;
// 计算k个骰子, 投出n的概率
public static void main(String []args) {
// 计算一个骰子, 投出各种数的次数
Map<NumToVal, Integer> numToVal = new HashMap<NumToVal, Integer>();
for(int i=1; i<=DICE_FACE_NUM; i++) {
numToVal.put(new NumToVal(1, i), 1);
}
int k = 2;
int n = 7;
calcNumToVal(numToVal, k);
// Log.log(numToVal);
if(logical(k, n) ) {
Log.log(numToVal.get(new NumToVal(k, n)) + " / " + Math.pow(DICE_FACE_NUM, k) );
} else {
Log.log("can't be...");
}
}
// i 表示有i个骰子, j表示和为j, cur表示第一个骰子投cur
// 思路 : i个骰子,投出和为j的概率为, 第1个骰子投cur 属于[curMin, curMax]的时候 剩余的(i-1)个骰子和为(j-cur)的次数
// 这里已知一个骰子, 投各种和的可能情况为1, 递推, 其余的2, 3, 4, ...的和为(i, i*6)的可能情况的个数
public static void calcNumToVal(Map<NumToVal, Integer> numToVal, int k) {
NumToVal tmp = new NumToVal();
for(int i=2; i<=k; i++) {
int sum = 0;
tmp.setDiceNum(i-1);
int maxVal = i * DICE_FACE_NUM;
int minVal = i;
for(int j=minVal; j<=maxVal; j++) {
int curMin = getMax(j - (i - 1) * 6, 1 );
int curMax = getMin(j - i + 1, DICE_FACE_NUM);
sum = 0;
for(int cur=curMin; cur<=curMax; cur++) {
tmp.setVal(j-cur);
sum += numToVal.get(tmp);
}
numToVal.put(new NumToVal(i, j), sum);
}
}
}
// 判断给定的n, k输入是否符合逻辑
private static boolean logical(int k, int n) {
if((n < k) || (n > DICE_FACE_NUM * k) ) {
return false;
}
return true;
}
// 获取x, y的较小者
private static int getMin(int x, int y) {
return x > y ? y : x;
}
// 获取x, y的较大者
private static int getMax(int x, int y) {
return x < y ? y : x;
}
// 封装了num个骰子, 和为tar的bean
static class NumToVal {
// 骰子的个数, 和为val
int diceNum;
int val;
// 初始化
public NumToVal() {
super();
}
public NumToVal(int diceNum, int val) {
this.diceNum = diceNum;
this.val = val;
}
// setter & getter
public int getDiceNum() {
return diceNum;
}
public void setDiceNum(int diceNum) {
this.diceNum = diceNum;
}
public int getVal() {
return val;
}
public void setVal(int val) {
this.val = val;
}
// 重写hashCode方法, 用于从集合中取出元素的判定
public int hashCode() {
return diceNum * 31 + val;
}
// 重写equals方法, 用于从集合中取出元素的判定
public boolean equals(Object obj) {
if(!(obj instanceof NumToVal) ) {
return false;
}
NumToVal other = (NumToVal) obj;
return (this.diceNum == other.diceNum) && (this.val == other.val);
}
// Debug
public String toString() {
return "diceNum : " + diceNum + " val : " + val;
}
}
}
效果截图
总结
其实在当初做这道面试题目的时候, 我虽然了解过动态规划, 但是 仅仅是了解
然而 最近因为啃了很久的算法导论, 看到这个场景 感觉也太像自底向上的dp了吧,,, 哈哈哈 真是人生何处不相逢!
注 : 因为作者的水平有限,必然可能出现一些bug, 所以请大家指出!