LeetCode 837.新 21 点:动态规划+滑动窗口

【LetMeFly】837.新 21 点:动态规划+滑动窗口

力扣题目链接:https://leetcode.cn/problems/new-21-game/

爱丽丝参与一个大致基于纸牌游戏 "21点" 规则的游戏,描述如下:

爱丽丝以 0 分开始,并在她的得分少于 k 分时抽取数字。 抽取时,她从 [1, maxPts] 的范围中随机获得一个整数作为分数进行累计,其中 maxPts 是一个整数。 每次抽取都是独立的,其结果具有相同的概率。

当爱丽丝获得 k或更多分 时,她就停止抽取数字。

爱丽丝的分数不超过 n 的概率是多少?

与实际答案误差不超过 10-5 的答案将被视为正确答案。

示例 1:

复制代码
输入:n = 10, k = 1, maxPts = 10
输出:1.00000
解释:爱丽丝得到一张牌,然后停止。

示例 2:

复制代码
输入:n = 6, k = 1, maxPts = 10
输出:0.60000
解释:爱丽丝得到一张牌,然后停止。 在 10 种可能性中的 6 种情况下,她的得分不超过 6 分。

示例 3:

复制代码
输入:n = 21, k = 17, maxPts = 10
输出:0.73278

提示:

  • 0 <= k <= n <= 104
  • 1 <= maxPts <= 104

解题方法:动态规划

这道题有点"反向dp"。令 d p i dpi dpi表示当爱丽丝获得 i i i分时,最终获胜的概率。

其中"获胜"是指最终分数 ≥ k \geq k ≥k且 ≤ n \leq n ≤n,那么初始状态0分时 d p 0 dp0 dp0即位所求。

一旦爱丽丝分数 ≥ k \geq k ≥k她就立即停止抽牌,由于最后一张牌的分数范围是 1 1 1到 m a x P t s maxPts maxPts,所以最终分数的可能范围是从 k k k到 k + m a x P t s − 1 k+maxPts-1 k+maxPts−1,可得dp数组的初始状态:

d p i = { 1 if i ≤ n 0 else , k ≤ i < k + m a x P t s dpi=\begin{cases} 1 \text{ if } i\leq n \\ 0 \text{ else } \end{cases}\ \ \ ,\ k\leq i\lt k+maxPts dpi={1 if i≤n0 else , k≤i<k+maxPts

那么在她手中的分数还未达到游戏终止时(假设当前为 i i i分),由于再抽一张牌可以等概率 达到 i + 1 , i + 2 , ⋯   , i + m a x P t s i+1, i+2, \cdots, i+maxPts i+1,i+2,⋯,i+maxPts,所以可得状态转移方程:

d p i = d p i + 1 + d p i + 2 + ⋯ + d p i + m a x P t s m a x P t s dpi = \frac{dpi+1+dpi+2+\dots+dpi+maxPts}{maxPts} dpi=maxPtsdpi+1+dpi+2+⋯+dpi+maxPts

由于每次计算 d p i + 1 + d p i + 2 + ⋯ + d p i + m a x P t s dpi+1+dpi+2+\dots+dpi+maxPts dpi+1+dpi+2+⋯+dpi+maxPts太过于机械和重复,所以可以参考"滑动窗口"的思想,使用一个变量 s s s记录"窗口"中 m a x P t s maxPts maxPts个元素的和,并随着窗口的前移不断更新 s s s即可。

  • 时间复杂度 O ( k + m a x P t s ) O(k+maxPts) O(k+maxPts)
  • 空间复杂度 O ( k + m a x P t s ) O(k+maxPts) O(k+maxPts)

AC代码

C++
cpp 复制代码
/*
 * @Author: LetMeFly
 * @Date: 2025-08-17 19:33:11
 * @LastEditors: LetMeFly.xyz
 * @LastEditTime: 2025-08-17 19:38:09
 */
class Solution {
public:
    double new21Game(int n, int k, int maxPts) {
        vector<double> dp(k + maxPts);
        double s = 0;
        for (int i = k; i < k + maxPts; i++) {
            dp[i] = i <= n;
            s += dp[i];
        }
        for (int i = k - 1; i >= 0; i--) {
            dp[i] = s / maxPts;
            s = s - dp[i + maxPts] + dp[i];
        }
        return dp[0];
    }
};
Python
python 复制代码
'''
Author: LetMeFly
Date: 2025-08-17 19:33:11
LastEditors: LetMeFly.xyz
LastEditTime: 2025-08-17 19:40:07
'''
class Solution:
    def new21Game(self, n: int, k: int, maxPts: int) -> float:
        dp = [0.] * (k + maxPts)
        s = 0.
        for i in range(k, k + maxPts):
            dp[i] = 1. if i <= n else 0.
            s += dp[i]
        for i in range(k - 1, -1, -1):
            dp[i] = s / maxPts
            s = s + dp[i] - dp[i + maxPts]
        return dp[0]
Java
java 复制代码
/*
 * @Author: LetMeFly
 * @Date: 2025-08-17 19:33:11
 * @LastEditors: LetMeFly.xyz
 * @LastEditTime: 2025-08-17 19:43:15
 */
class Solution {
    public double new21Game(int n, int k, int maxPts) {
        double[] dp = new double[k + maxPts];
        double s = 0;
        for (int i = k; i < k + maxPts; i++) {
            dp[i] = i <= n ? 1. : 0.;
            s += dp[i];
        }
        for (int i = k - 1; i >= 0; i--) {
            dp[i] = s / maxPts;
            s = s + dp[i] - dp[i + maxPts];
        }
        return dp[0];
    }
}
Go
go 复制代码
/*
 * @Author: LetMeFly
 * @Date: 2025-08-17 19:33:11
 * @LastEditors: LetMeFly.xyz
 * @LastEditTime: 2025-08-17 22:20:30
 */
package main

func new21Game(n int, k int, maxPts int) float64 {
    dp := make([]float64, k + maxPts)
    s := 0.
    for i := k; i < k + maxPts; i++ {
        if i <= n {
            dp[i] = 1.
        } else {
            dp[i] = 0.
        }
        s += dp[i]  // 别忘了
    }
    for i := k - 1; i >= 0; i-- {
        dp[i] = s / float64(maxPts)
        s = s + dp[i] - dp[i + maxPts]
    }
    return dp[0]
}
Rust
rust 复制代码
/*
 * @Author: LetMeFly
 * @Date: 2025-08-17 19:33:11
 * @LastEditors: LetMeFly.xyz
 * @LastEditTime: 2025-08-17 22:31:00
 */
impl Solution {
    pub fn new21_game(n: i32, k: i32, max_pts: i32) -> f64 {
        let k: usize = k as usize;
        let max_pts: usize = max_pts as usize;
        let n: usize = n as usize;

        let mut dp: Vec<f64> = vec![0 as f64; k + max_pts];
        let mut s: f64 = 0.;
        for i in k..(k+max_pts) {
            if i <= n {
                dp[i] = 1.;
            } else {
                dp[i] = 0.;
            }
            s += dp[i];
        }
        for i in (0..k).rev() {
            dp[i] = s / max_pts as f64;
            s = s + dp[i] - dp[i + max_pts];
        }
        dp[0]
    }
}

同步发文于CSDN和我的个人博客,原创不易,转载经作者同意后请附上原文链接哦~

千篇源码题解已开源

相关推荐
白狐_79829 分钟前
408数据结构第5章:树与二叉树②——遍历、线索树、森林与哈夫曼
数据结构·算法·深度优先
致Great4 小时前
科研人的 AI,不该只回答问题:我用字节TraeWork 跑了一遍真实研究任务
算法
纵有疾風起4 小时前
线性表的定义与基本操作 — 从逻辑结构到 ADT 接口
数据结构·算法·408·线性表·adt
测试_AI_一辰4 小时前
AI Agent 评测最隐蔽的坑-记忆
人工智能·算法·ai·自动化·ai编程
lucas_AI5 小时前
给YOLO检测器插 LoRA,'插对地方'比'插什么'更要命
人工智能·算法
LuminousCPP5 小时前
数据结构-时间与复杂度|时间/空间复杂度 + 两道力扣练习 + 二分查找复盘
c语言·数据结构·经验分享·算法·leetcode
Nil2085 小时前
leetcode 三数之和
数据结构·算法·leetcode
小小龙学IT5 小时前
C++ std::vector 底层实现深度解析:内存布局、扩容策略、移动语义与迭代器失效
开发语言·c++·算法
noipp6 小时前
推荐题目:洛谷 P16689 出征
java·开发语言·数据结构·c++·算法·洛谷·luogu
eBest数字化转型方案6 小时前
从工程视角拆解冰柜资产管理:拍照识别 pipeline、纯净度算法与 IoT 选型踩坑
人工智能·物联网·算法