每日一题算法:2020年6月3日 新21点new21Game(没做出来)

2020年6月3日 新21点new21Game

每日一题算法:2020年6月3日 新21点new21Game(没做出来)_第1张图片
默认格式:

class Solution {
    public double new21Game(int N, int K, int W) {

    }
}

解题思路:

按照我的想法,看完题目的时候我觉得不是找一个公式直接带入就可以实现了吗?三个变量

K,N,W肯定存在能够推出概率公式啊。

所以我开始理解题目:

1,他并不在乎你前面抽取了多少次,只是在乎你是超过k这个值的时候的数值,所以我们需要想一想,在出现超过K的那一次之前,分数可能是多少。

分数的可能是在k-w到k-1之间,而k-w到k-1之间的值的概率是多少,是简单的1/w吗?

然后我开始用简单的值来测试,他们之间的关系

K=7,W=3,每次从1-3之间抽取一个数,然后我们测试4-6之间各值出现的概率。

出现6的所有情况

111111(六个1)

12111(四个1一个2)这种情况可能有5种

1311(三个1一个3)这种情况有可能有4种

打住打住,这样子是不可能算出结果的。这时,我突然想到一个事情,概率树是不是和我们学的一种叫做树的结构很像啊?

2,如果我们直接根据W建立一个W叉树,如果叶子节点的值大于K时,我们判断他和N的大小,但是这样就有可能产生一个万叉树,所以我决定,使用递归来模拟一个树的结构。

每日一题算法:2020年6月3日 新21点new21Game(没做出来)_第2张图片

当一个数字小于K时,让他的层数+1,并且遍历增加1-W的每个值,查看结果是否大于N,如果大于N,概率增加1/W^层数次。

这样的方式时间复杂度是O(n^2),好恐怖,我还是想想有没有更好的方法吧

3,在暴力算法的基础上进行优化

我们画出一个概率树,来推算一个数出现的概率和K W N之间的关系

每日一题算法:2020年6月3日 新21点new21Game(没做出来)_第3张图片

画完图之后我发现这道题目比我想象得还要复杂许多

我们有数字123,然后我们需要计算出现各数字的概率

最后得出的结论,这道题一定是用递归做的,他之间有一个公式,但是不是直接带入就能得到结果的,需要使用递归来计算结果。而现在要做的是就是找出这个公式,然后优化之前的暴力算法。

还是没找出规则,太复杂了。

4,再画一张图,把特殊的部分都标出来

每日一题算法:2020年6月3日 新21点new21Game(没做出来)_第4张图片

我们把这个概率树想象成一个三角形我们从其中找出几个关键点,目的是为了寻找出现K-W到K-1中间各值出现的概率

  1. 头顶是0,表示开始,第一层表示的是1-W,也就是每层抽取W种牌的概率是相等的。
  2. 最左下角是K,也就是出现K的最小的概率所在的位置,也是需要计算概率的最深的层数,再往后无论之前如何抽取,其值必定大于K
  3. 往回一层是K-1,是出现K之前最深的一层,意味着小于K的数出现的最深的层数。这一层中只有一个值符合要求,得到的概率是W^(K-1)其中符合大于N的要求的是(W)^K-2*(N-K)/W
  4. 往回两层是K-2,有两个符合小于K-W到 K要求的数,分别是K-2,K-1,概率是W^(K-2)
  5. 再往后是3个,4个5个,当在第K-W层时最多,这一层有W个符合K-W到K之间的值
  6. 上面是第一段,还有第二段
  7. 第二段后面还有第三段
  8. 太麻烦了不算了,这个还是需要数学专业的人来算吧,我们学计算机的直接按照要求写一下随机数,直接通过随机数来测试概率。

直接抄,这题不想看了,花了我4个小时,思路是理清楚了,但是公式也太复杂了,一下子就会写错,太麻烦了。

public double new21Game(int N, int K, int W) {
    if (K == 0) {
        return 1.0;
    }
    double[] dp = new double[K + W];
    for (int i = K; i <= N && i < K + W; i++) {
        dp[i] = 1.0;
    }
    dp[K - 1] = 1.0 * Math.min(N - K + 1, W) / W;
    for (int i = K - 2; i >= 0; i--) {
        dp[i] = dp[i + 1] - (dp[i + W + 1] - dp[i + 1]) / W;
    }
    return dp[0];
}

你可能感兴趣的:(每日一题算法)