爱丽丝参与一个大致基于纸牌游戏 “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
我们设计一个函数
函数
- 如果
$i \ge k$ ,那么停止抽取数字,如果$i \le n$ ,返回$1$ ,否则返回$0$ ; - 否则,可以在
$[1,..maxPts]$ 范围内抽取下一个数字$j$ ,那么$dfs(i) = \frac{1}{maxPts} \sum_{j=1}^{maxPts} dfs(i+j)$ 。
这里我们可以使用记忆化搜索来加速计算。
以上方法的时间复杂度为
当
当
因此,当
即:
如果
我们假设有
综上所述,有以下状态转移方程:
时间复杂度
class Solution:
def new21Game(self, n: int, k: int, maxPts: int) -> float:
@cache
def dfs(i: int) -> float:
if i >= k:
return int(i <= n)
if i == k - 1:
return min(n - k + 1, maxPts) / maxPts
return dfs(i + 1) + (dfs(i + 1) - dfs(i + maxPts + 1)) / maxPts
return dfs(0)
class Solution {
private double[] f;
private int n, k, maxPts;
public double new21Game(int n, int k, int maxPts) {
f = new double[k];
this.n = n;
this.k = k;
this.maxPts = maxPts;
return dfs(0);
}
private double dfs(int i) {
if (i >= k) {
return i <= n ? 1 : 0;
}
if (i == k - 1) {
return Math.min(n - k + 1, maxPts) * 1.0 / maxPts;
}
if (f[i] != 0) {
return f[i];
}
return f[i] = dfs(i + 1) + (dfs(i + 1) - dfs(i + maxPts + 1)) / maxPts;
}
}
class Solution {
public:
double new21Game(int n, int k, int maxPts) {
vector<double> f(k);
function<double(int)> dfs = [&](int i) -> double {
if (i >= k) {
return i <= n ? 1 : 0;
}
if (i == k - 1) {
return min(n - k + 1, maxPts) * 1.0 / maxPts;
}
if (f[i]) {
return f[i];
}
return f[i] = dfs(i + 1) + (dfs(i + 1) - dfs(i + maxPts + 1)) / maxPts;
};
return dfs(0);
}
};
func new21Game(n int, k int, maxPts int) float64 {
f := make([]float64, k)
var dfs func(int) float64
dfs = func(i int) float64 {
if i >= k {
if i <= n {
return 1
}
return 0
}
if i == k-1 {
return float64(min(n-k+1, maxPts)) / float64(maxPts)
}
if f[i] > 0 {
return f[i]
}
f[i] = dfs(i+1) + (dfs(i+1)-dfs(i+maxPts+1))/float64(maxPts)
return f[i]
}
return dfs(0)
}
function new21Game(n: number, k: number, maxPts: number): number {
const f = new Array(k).fill(0);
const dfs = (i: number): number => {
if (i >= k) {
return i <= n ? 1 : 0;
}
if (i === k - 1) {
return Math.min(n - k + 1, maxPts) / maxPts;
}
if (f[i] !== 0) {
return f[i];
}
return (f[i] = dfs(i + 1) + (dfs(i + 1) - dfs(i + maxPts + 1)) / maxPts);
};
return dfs(0);
}
我们可以将方法一中的记忆化搜索改成动态规划。
定义
当
当
当
时间复杂度
class Solution:
def new21Game(self, n: int, k: int, maxPts: int) -> float:
f = [0] * (k + maxPts)
for i in range(k, min(n + 1, k + maxPts)):
f[i] = 1
f[k - 1] = min(n - k + 1, maxPts) / maxPts
for i in range(k - 2, -1, -1):
f[i] = f[i + 1] + (f[i + 1] - f[i + maxPts + 1]) / maxPts
return f[0]
class Solution {
public double new21Game(int n, int k, int maxPts) {
if (k == 0) {
return 1.0;
}
double[] f = new double[k + maxPts];
for (int i = k; i < Math.min(n + 1, k + maxPts); ++i) {
f[i] = 1;
}
f[k - 1] = Math.min(n - k + 1, maxPts) * 1.0 / maxPts;
for (int i = k - 2; i >= 0; --i) {
f[i] = f[i + 1] + (f[i + 1] - f[i + maxPts + 1]) / maxPts;
}
return f[0];
}
}
class Solution {
public:
double new21Game(int n, int k, int maxPts) {
if (k == 0) {
return 1.0;
}
double f[k + maxPts];
memset(f, 0, sizeof(f));
for (int i = k; i < min(n + 1, k + maxPts); ++i) {
f[i] = 1;
}
f[k - 1] = min(n - k + 1, maxPts) * 1.0 / maxPts;
for (int i = k - 2; i >= 0; --i) {
f[i] = f[i + 1] + (f[i + 1] - f[i + maxPts + 1]) / maxPts;
}
return f[0];
}
};
func new21Game(n int, k int, maxPts int) float64 {
if k == 0 {
return 1
}
f := make([]float64, k+maxPts)
for i := k; i < min(n+1, k+maxPts); i++ {
f[i] = 1
}
f[k-1] = float64(min(n-k+1, maxPts)) / float64(maxPts)
for i := k - 2; i >= 0; i-- {
f[i] = f[i+1] + (f[i+1]-f[i+maxPts+1])/float64(maxPts)
}
return f[0]
}
function new21Game(n: number, k: number, maxPts: number): number {
if (k === 0) {
return 1;
}
const f = new Array(k + maxPts).fill(0);
for (let i = k; i < Math.min(n + 1, k + maxPts); ++i) {
f[i] = 1;
}
f[k - 1] = Math.min(n - k + 1, maxPts) / maxPts;
for (let i = k - 2; i >= 0; --i) {
f[i] = f[i + 1] + (f[i + 1] - f[i + maxPts + 1]) / maxPts;
}
return f[0];
}