[837. 新21点]
难度 中等
爱丽丝参与一个大致基于纸牌游戏 “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
,则该答案将被视为正确答案通过。 - 此问题的判断限制时间已经减少。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/new-21-game
解法:动态规划
根据规则,当分数达到或超过 K
时游戏结束,游戏结束时,如果分数不超过 N
则获胜,如果分数超过 N
则失败。因此当 K ≤ x ≤ min(N, K + W − 1)
时有 dp[x] = 1
,当 x > min(N, K + W − 1
) 时有 dp[x] = 0
。
则有状态转移方程为 :
dp[x] = (1 / W) * (dp[x + 1] + dp[x + 2] + ... + dp[x + W])
例如:
class Solution {
public double new21Game(int N, int K, int W) {
if(K == 0) {
return 1.0;
}
double[] dp = new double[K + W];
//从 K 到 N 的值为 1
for(int i = K; i <= N && i < K + W; i++ ) {
dp[i] = 1.0;
}
for(int i = K - 1; i >= 0; i--) {
double temp = 0;
// 计算从 dp(x + 1) + dp(x + 2) + ... + dp(x + w)
for(int j = 1; j <= W; j++) {
temp += dp[i + j];
}
dp[i] = temp / W; // 计算从 dp(x) = 1 / W * (dp(x + 1) + dp(x + 2) + ... + dp(x + w))
}
return dp[0];
}
}
改进:
上述求 dp[x]
的过程中通过循环来求 dp(x + 1) + dp(x + 2) + ... + dp(x + w)
,而dp[x]
满足下面关系:
dp[x] = (1 / W) * (dp[x + 1] + dp[x + 2] + ... + dp[x + W])
dp[x - 1] = (1 / W) * (dp[x] + dp[x + 1] + ... + dp[x + W - 1])
则 dp[x] - dp[x - 1] = (1 / W) * (dp[x + W]) - dp[x])
移项得 dp[x - 1] = dp[x] - (1 / W) * (dp[x + W]) - dp[x])
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 + 1 + W] - dp[i + 1]) / W;
}
return dp[0];
}
}