力扣刷题-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 分。
来源:力扣(LeetCode)

    微信搜索关注公众号“编程猿来如此”,获取最新文章。

题意分析

    过程模拟:抽一个数字累计到当前的总积分 i ,然后做判断,如果 i 小于 K 则继续抽下一个数字,直到累计积分大于等于 K 则停止整个过程。
当前的积分 i 是在上一步的基础上抽取一个数字得到的,有以下几种情况:

  • 上一步积分是 i-1,然后抽取数字 1 后得到 i,概率为: 【得到 i-1 分的概率】*1/W
  • 上一步积分是 i-2,然后抽取数字 2 后得到 i,概率为: 【得到 i-2 分的概率】*1/W
  • 上一步积分是 i-3,然后抽取数字 3 后得到 i,概率为: 【得到 i-3 分的概率】*1/W
  • 上一步积分是 i-W,然后抽取数字 W 后得到 i,概率为: 【得到 i-3 分的概率】*1/W

    因此得到当前积分 i 的概率为以上所有情况的概率之和。不超过N的概率即为从 K 到 N 各概况之和。

动态规划

    当前的状态是由之前已经发生的状态值决定的,因此可以使用动态规划来解决问题:

  • dp[i] 表示得到积分 i 的概率,根据题意 i 最大值为 N,因此 dp 长度为 N+1;
  • dp[i] = ( dp[i-1]+dp[i-2]+…dp[i-W] ) /W ;

    然后从 K 到 N 进行dp[i] 求和即为本题的结果。

代码实现

class Solution {
public:
    double new21Game(int N, int K, int W) {
      vector<double> dp(N+1, 0);//初始化dp
      dp[0] = 1;// 积分 0 的概率为1
        for(int i = 1;i <= N;i++ )
        {
          // [i-W,i-1]求和,除以 W
            for( int j = 1;j<= W ;j++)
            {
                //到 K 就停止了,因此区间不能超出 K
                if( i -j < K && i- j >= 0)
                {
                    dp[i] += 1.00000 / W * dp[i - j];
                }
            }
        }
        double res = 0;
        for(int i = K;i<=N;i++)//[K,N]求和
        {
            res+=dp[i];
        }
        return res;
    }
};

优化重复过程

    在求 dp[i] 的过程中,每个值都计算了 i-W到 i-1 的和,存在重复的计算过程,可以进行优化。区间 [i-W,i-1] 就是一个固定大小为 W 的窗口,每次向右移动一步后,区间和是在之前的基础上右侧减少一个元素,左侧增加一个元素。

class Solution {
public:
    double new21Game(int N, int K, int W) {
        vector<double> dp(N+1, 0);
      dp[0] = 1;
        double presum = 0;//表示之前 W 个元素的概率和
        for(int i = 1;i <= N;i++ )
        {
            if( i - W  -1 >= 0 )
                presum -= dp[ i-W -1];//右侧移除一个元素
            if( i -1 < K)
                presum += dp[ i-1];//左侧加入一个元素
            dp[i] = presum * 1.00000/W;            
        }
        double res = 0;
        for(int i = K;i<=N;i++)
        {
            res+=dp[i];
        }
        return res;
    }
};

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值