LeetCode 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
解法:动态规划
解题思路:
一开始我以为这是一个排列组合的问题,因此写出这样的代码:
class Solution {
double exceed = 0;
double under = 0;
public double new21Game(int N, int K, int W) {
cal(0,N,K,W);
return under/(exceed+under);
}
public void cal(int C, int N, int K, int W)
{
if(C>=K && C<=N)
{
under++;
return;
}
if(C>=K && C>N)
{
exceed++;
return;
}
for(int i=1; i<=W; i++)
{
cal(C+i,N,K,W);
}
}
}
结果是错误的,因为每一轮抽牌其实是独立的,也就是我可能第一轮抽到的牌与第二轮抽到的牌是一模一样的(如第一轮抽到四张1,第二轮也可能抽到四张1),但是如果用排列组合的话,每一轮抽牌的结果却是唯一的(第一轮抽到四张1,第二轮按代码来只能抽到三张1加一张2,不可能抽到四张1),因此我们使用动态规划的思想来解题
用dp[x]数组表示从x点数开始赢得游戏的概率
我们重新定义规则,当我们点数超过K但不超过N时赢,当点数超过K且超过N时输,也就是如下式子
K <= x <= Min(N,K+W-1) dp[x]=1
x > Min(N,K+W-1) dp[x]=0
Min(N,K+W-1)
作为分界线的原因就是,当前点数最多为K-1
,在这种情况下我们的点数最多就是K+W-1
,因为最大的牌就是W
,这时候如果小于Min(N,K+W-1)
,那必然小于N
,所以能赢,注意第二个条件,因为不可能大于K+W-1
,所以条件成立的话只能大于N
,因此输
因此我们可以得到转移方程
dp[x] = (dp[x+1]+dp[x+2]+...+dp[x+W])/W
代码如下:
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;
}
for (int i = K - 1; i >= 0; i--) {
for (int j = 1; j <= W; j++) {
dp[i] += dp[i + j] / W;
}
}
return dp[0];
}
}
代码还可以进行优化:
因为
dp[x] = (dp[x+1]+dp[x+2]+...+dp[x+W])/W
dp[x+1] = (dp[x+2]+dp[x+3]+...+dp[x+W+1])/W
因此两式相减有
dp[x] = dp[x+1] - (dp[x+W+1]-dp[x+1])/W
上式就是新的转移方程,只使用于1<= x <K-1
,因为dp[K+W]不存在,不会直接从点数K开始游戏
dp[K-1]可以通过第一个转移方程得到即
dp[x] = (dp[x+1]+dp[x+2]+...+dp[x+W])/W
或者
dp[K-1] = Min(N-K+1, W)/W
代码如下:
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];
}
}
总结
做这道题的时候一直在想,最优子解在哪,怎么求,这道题的最优子解应该就是我抽下一张牌赢的概率就是拿下每一张牌时赢的概率的平均值
解法来源
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/new-21-game/solution/xin-21dian-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。