2020年6月3日 新21点new21Game
默认格式:
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的大小,但是这样就有可能产生一个万叉树,所以我决定,使用递归来模拟一个树的结构。
当一个数字小于K时,让他的层数+1,并且遍历增加1-W的每个值,查看结果是否大于N,如果大于N,概率增加1/W^层数次。
这样的方式时间复杂度是O(n^2),好恐怖,我还是想想有没有更好的方法吧
3,在暴力算法的基础上进行优化
我们画出一个概率树,来推算一个数出现的概率和K W N之间的关系
画完图之后我发现这道题目比我想象得还要复杂许多
我们有数字123,然后我们需要计算出现各数字的概率
最后得出的结论,这道题一定是用递归做的,他之间有一个公式,但是不是直接带入就能得到结果的,需要使用递归来计算结果。而现在要做的是就是找出这个公式,然后优化之前的暴力算法。
还是没找出规则,太复杂了。
4,再画一张图,把特殊的部分都标出来
我们把这个概率树想象成一个三角形我们从其中找出几个关键点,目的是为了寻找出现K-W到K-1中间各值出现的概率
- 头顶是0,表示开始,第一层表示的是1-W,也就是每层抽取W种牌的概率是相等的。
- 最左下角是K,也就是出现K的最小的概率所在的位置,也是需要计算概率的最深的层数,再往后无论之前如何抽取,其值必定大于K
- 往回一层是K-1,是出现K之前最深的一层,意味着小于K的数出现的最深的层数。这一层中只有一个值符合要求,得到的概率是W^(K-1)其中符合大于N的要求的是(W)^K-2*(N-K)/W
- 往回两层是K-2,有两个符合小于K-W到 K要求的数,分别是K-2,K-1,概率是W^(K-2)
- 再往后是3个,4个5个,当在第K-W层时最多,这一层有W个符合K-W到K之间的值
- 上面是第一段,还有第二段
- 第二段后面还有第三段
- 太麻烦了不算了,这个还是需要数学专业的人来算吧,我们学计算机的直接按照要求写一下随机数,直接通过随机数来测试概率。
直接抄,这题不想看了,花了我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];
}