原题
爱丽丝参与一个大致基于纸牌游戏 “21点” 规则的游戏,描述如下:
爱丽丝以 0
分开始,并在她的得分少于 K
分时抽取数字。 抽取时,她从 [1, W]
的范围中随机获得一个整数作为分数进行累计,其中 W
是整数。 每次抽取都是独立的,其结果具有相同的概率。
当爱丽丝获得不少于 K
分时,她就停止抽取数字。 爱丽丝的分数不超过 N
的概率是多少?
示例 1:
输入: N = 10, K = 1, W = 10
输出: 1.00000
说明: 爱丽丝得到一张卡,然后停止。
示例 2:
输入: N = 6, K = 1, W = 10
输出: 0.60000
说明: 爱丽丝得到一张卡,然后停止。
在 W = 10 的 6 种可能下,她的得分不超过 N = 6 分。
示例 3:
输入: N = 21, K = 17, W = 10
输出: 0.73278
提示:
0 <= K <= N <= 10000
1 <= W <= 10000
- 如果答案与正确答案的误差不超过
10^-5
,则该答案将被视为正确答案通过。 - 此问题的判断限制时间已经减少。
解法
思想
可以从dfs的角度入手,这是自顶向下的解法,但比较耗时,
于是可以转换为动态规划。
代码
未经优化的DFS,计算了很多重复单元:
class Solution {
int K;
int W;
int N;
public double new21Game(int N, int K, int W) {
this.K = K;
this.W = W;
this.N = N;
return dfs(0,1.0);
}
public double dfs(int cur,double p){
if(cur>=K){
if(cur<=N){
return p;
}
return 0.0;
}
double ans = 0.0;
for(int i = 1;i<=W;i++){
ans += dfs(cur+i,p/W);
}
return ans;
}
}
加入记忆化后的DFS,仍然超时:
class Solution {
int K;
int W;
int N;
Double[][] memory;
public double new21Game(int N, int K, int W) {
this.K = K;
this.W = W;
this.N = N;
memory = new Double[K][K];
return dfs(0,0);
}
public double dfs(int cur,int depth){
if(cur>=K){
if(cur<=N){
return Math.pow(1.0/W,depth);
}
return 0.0;
}
if(memory[cur][depth]!=null) return memory[cur][depth];
double ans = 0.0;
for(int i = 1;i<=W;i++){
ans += dfs(cur+i,depth+1);
}
memory[cur][depth] = ans;
return ans;
}
}
那么需要进一步优化,通过分析发现,这里的概率其实仅和cur
有关,即memory[cur][depth] == Math.pow(1.0/W,depth) * memory[cur][0]
,因此只需要计算出memory[0][0],memory[1][0],memory[2][0]...
就能完成大部分计算。
于是可以使用动态规划来做(用dfs会特别麻烦):
class Solution {
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];
}
}
原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/leetcode837-%e6%96%b021%e7%82%b9/