【问题标题】:How to calculate the probability of getting the sum X using N six-sided dice如何计算使用 N 个六面骰子得到总和 X 的概率
【发布时间】:2020-06-17 03:34:08
【问题描述】:

挑战: 例如,当使用 3 个六面骰子时,得到 15 和的概率是多少。例如,这可以通过获得 5-5-5 或 6-6-3 或 3-6-6 或更多选项来实现。

2个骰子的强力解决方案-复杂度为6^2:

假设我们只有 2 个六面骰子,我们可以编写一个非常基本的代码:

public static void main(String[] args) {
   System.out.println(whatAreTheOdds(7));
}

public static double whatAreTheOdds(int wantedSum){
    if (wantedSum < 2 || wantedSum > 12){
        return 0;
    }

    int wantedFound = 0;
    int totalOptions = 36;

    for (int i = 1; i <= 6; i++) {
        for (int j = 1; j <= 6; j++) {
            int sum = i+j;
            if (sum == wantedSum){
                System.out.println("match: " + i  + " " + j );
                wantedFound +=1;
            }
        }
    }

    System.out.println("combinations count:" + wantedFound);
    return (double)wantedFound / totalOptions;
}

7 的输出将是:

匹配:1 6

匹配:2 5

匹配:3 4

匹配:4 3

匹配:5 2

匹配:6 1

组合数:6

0.16666666666666666

问题是如何泛化算法来支持N个骰子:

public static double whatAreTheOdds(int wantedSum, int numberOfDices)

因为我们不能动态创建嵌套的for 循环,所以我们必须采用不同的方法。

我想到了类似的东西:

 public static double whatAreTheOdds(int sum, int numberOfDices){

    int sum;
    for (int i = 0; i < numberOfDices; i++) {
        for (int j = 1; j <= 6; j++) {

        }
    }
}

但未能提出正确的算法。

这里的另一个挑战是 - 有没有一种方法可以有效地做到这一点,而不是 6^N 的复杂性?

【问题讨论】:

  • 递归可能是最简单的方法。或者一个计数器,以 6 为基数,有 n 个数字表示 n 个骰子。
  • 很确定有一个封闭形式的解决方案?
  • 7-7-1怎么来的?一个从 1 到 6 编号的骰子。
  • @ManojBanik 哎呀。修好了。

标签: java algorithm time-complexity probability dice


【解决方案1】:

这个想法是有一个数组存储每个骰子的当前“状态”,每个骰子从一个开始,然后向上计数。例如,使用三个骰子,您将生成组合:

111
112
...
116
121
122
...
126
...
665
666

一旦你有了一个状态,你就可以很容易地找到总和是否是你要找的那个。

我把细节留给你,因为这似乎是一个有用的学习练习:)

【讨论】:

  • 这种方法的时间复杂度是O(6^Y)。即使是很小的Y,这也会很快变得棘手。
  • @btilly 我知道,我只是在回答 OP 的问题,同时保持他们的计数方法
【解决方案2】:

您可能想看看 Wolfram article 以了解完全不同的方法,它使用单个循环计算所需的概率。

【讨论】:

    【解决方案3】:

    这是一个递归解决方案,通过记忆来计算组合。

    import java.util.Arrays;
    import java.lang.Math;
    
    class Dices {
        public static final int DICE_FACES = 6;
    
        public static void main(String[] args) {
            System.out.println(whatAreTheOdds(40, 10));
        }
    
        public static double whatAreTheOdds(int sum, int dices) {
            if (dices < 1 || sum < dices || sum > DICE_FACES * dices) return 0;
    
            long[][] mem = new long[dices][sum];
            for (long[] mi : mem) {
                Arrays.fill(mi, 0L);
            }
            long n = whatAreTheOddsRec(sum, dices, mem);
            return n / Math.pow(DICE_FACES, dices);
        }
    
        private static long whatAreTheOddsRec(int sum, int dices, long[][] mem) {
            if (dices <= 1) {
                return 1;
            }
            long n = 0;
            int dicesRem = dices - 1;
            int minFace = Math.max(sum - DICE_FACES * dicesRem, 1);
            int maxFace = Math.min(sum - dicesRem, DICE_FACES);
            for (int i = minFace; i <= maxFace; i++) {
                int sumRem = sum - i;
                long ni = mem[dicesRem][sumRem];
                if (ni <= 0) {
                    ni = whatAreTheOddsRec(sumRem, dicesRem, mem);
                    mem[dicesRem][sumRem] = ni;
                }
                n += ni;
            }
            return n;
        }
    }
    

    输出:

    0.048464367913724195
    

    编辑:为了记录,这个算法的复杂度仍然是 O(6^n),这个答案只是为了给一般情况下一个可能的实现,它比最简单的实现更好,使用记忆和搜索空间修剪(仅探索可行的解决方案)。

    【讨论】:

    • 顺便说一句,要计算分母,为什么不在Java中简单地使用long m = (long) Math.pow(DICE_FACES, dices);
    • @EricWang 这是一个很好的观点,我默认使用精确的整数计算(尽管实际上这样做是使用repeated squaring 的快速方法),但由于结果将被转换double 无论如何对于概率计算它不是很有用......
    【解决方案4】:

    正如Alex's answer 所说,有一个组合公式:

    在这个公式中,p 是掷出的数字的总和(问题中的 X),n 是骰子的数量,s 是每个骰子的面数(问题中的 6)。无论是使用循环评估二项式系数,还是使用帕斯卡三角形预先计算,如果我们将 s = 6 设为常数,将 X - n 设为 O,则无论哪种方式,时间复杂度都是 O(n2) (n)。


    这是一种替代算法,它一次计算所有概率。这个想法是使用discrete convolution 来计算给定分布的两个随机变量之和的分布。通过使用 exponentiation by squaring 算法中的分而治之方法,我们只需要进行 O(log n) 次卷积。

    伪代码如下; sum_distribution(v, n) 返回一个数组,其中索引 X - n 处的值是n 掷骰子的总和为 X 的组合数。

    // for exact results using integers, let v = [1, 1, 1, 1, 1, 1]
    // and divide the result through by 6^n afterwards
    let v = [1/6.0, 1/6.0, 1/6.0, 1/6.0, 1/6.0, 1/6.0]
    
    sum_distribution(distribution, n)
        if n == 0
            return [1]
        else if n == 1
            return v
        else
            let r = convolve(distribution, distribution)
            // the division here rounds down
            let d = sum_distribution(r, n / 2)
            if n is even
                return d
            else
                return convolve(d, v)
    

    卷积不能在线性时间内完成,因此运行时间主要由长度为 3n 的两个数组上的最后一个卷积决定,因为其他卷积位于足够短的数组上。

    这意味着如果你使用简单的卷积算法,计算所有概率需要 O(n2) 时间,如果你使用fast Fourier transform,则需要 O( n log n) 时间。

    【讨论】:

      猜你喜欢
      • 2021-07-14
      • 2016-07-08
      • 1970-01-01
      • 2021-10-28
      • 1970-01-01
      • 2015-07-03
      • 2020-03-22
      • 2021-09-23
      • 2020-09-02
      相关资源
      最近更新 更多