No.837 LeetCode题目 “新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,则该答案将被视为正确答案通过。
此问题的判断限制时间已经减少。

解题思路

本题利用动态规划的思想,令dp[i]表示当手里的数字为i时获胜的概率,本题要求dp[0]
因为手中牌最大为K+W-1(手中牌为K-1,又抽中了W),因此初始化一个长度为K+W的数组。

  • 当手中的牌当N >= i >= K时,游戏结束,获得胜利(返回1)。当i>N时,游戏失败(返回0)。
  • 当手中的牌i <= K-1时,dp[i] = (dp[i+1]+...+dp[i+W]) / W

由于利用循环求和dp[i+1]+...+dp[i+W]时会超出时间限制,因此我们利用窗口效应,设一个变量window来记录当前的dp[i+1]+...+dp[i+W]i每向前移动一格,窗口也会跟着移动一格。如下图所示:
在这里插入图片描述
上图参考:
https://leetcode-cn.com/problems/new-21-game/solution/

具体实现细节详见伪代码。

完整代码

class Solution {
public:
    double new21Game(int N, int K, int W) {
      //如果K为0,最后肯定可以成功。
      if(K == 0){
        return 1.0;
      }
      //利用动态规划的思想,dp[i]表示当手里的数字为i时获胜的概率,本题要求dp[0]。
      //手中牌最大为K+W-1,因此初始化一个长度为K+W的数组。
      double dp[K+W];
      /*for(int i =0;i<K+W-1;i++){
        dp[i] = 0;
      }*/
      double window = 0.0;
      //当N>=i>=K时,游戏结束,获得胜利。当i>N时,游戏失败。
      for(int i = K;i<K+W;i++){
        dp[i] = (i > N) ? 0 : 1.0;
        window += dp[i];//利用window值记录这一总和(滑动窗口原理)
      }
      //当i<=K-1时,dp[i] = (dp[i+1]+...+dp[i+W])/W
      for(int i = K-1;i>=0;i--){
        dp[i] = window/W;
        window += dp[i];
        window -= dp[i+W];
      }
      return dp[0];
    }
};

性能结果

在这里插入图片描述

个人感悟

做了一些动态规划的题,个人感觉确实是状态转移方程最难写。
有些时候需要求dp[i],比如No.194 LeetCode题目 “打家劫舍”
有些时候需要求dp[0],比如本题。

这两种思路主要是要考虑从前往后计算,还是从后往前计算。
这道题就是要从后往前计算,因为后一状态决定了前一状态。

同时dp[i]这个物理量如何解释也是要思考的问题之一,之后遇到动态规划还要多多练习。

猜你喜欢

转载自blog.csdn.net/qq_39856931/article/details/106533143