The New 21 Game problem asks you to compute the probability that Alice’s score is at most n
when she stops drawing numbers in a game. The game works as follows:
maxPts
(inclusive) to her score, each with equal probability.k
.n
(the maximum score to be considered a "win"), k
(the threshold at which Alice stops), and maxPts
(the maximum number Alice can draw in a single turn).
The goal is to compute the probability that Alice's final score is less than or equal to n
.
Constraints:
n
≤ 104k
≤ 104maxPts
≤ 104
At first glance, this problem seems to require simulating all possible game outcomes. For each possible sequence of draws, we could track the probability of reaching every possible total score. However, as the number of possible sequences grows exponentially with k
and maxPts
, brute-force simulation is infeasible for large inputs.
Instead of simulating every path, we can use dynamic programming: for each possible score, we can calculate the probability of reaching that score, and then use those probabilities to compute the probabilities for higher scores. The key insight is that the probability of reaching a certain score depends only on the probabilities of reaching previous scores.
To optimize, we notice that for each score x
less than k
, the probability of reaching x
can be expressed as the average of the probabilities of reaching the previous maxPts
scores. This observation allows us to use a sliding window sum to efficiently compute probabilities for all scores up to n
.
We use dynamic programming to solve this problem efficiently. Let’s break down the steps:
dp[x]
be the probability that Alice gets a score of exactly x
.dp[0] = 1.0
, since Alice starts at 0 with probability 1.x < k
, Alice can draw again. For each possible draw (1 to maxPts
), the probability of reaching x + i
is dp[x] / maxPts
for each i
.[x - maxPts, x - 1]
.k
. So, for scores in the range [k, n]
, we sum up dp[x]
to get the final answer.k == 0
or n >= k + maxPts - 1
, Alice can always reach a score ≤ n
with probability 1.0.dp[0]
to 1.0 and use a variable windowSum
to keep track of the sum of the previous maxPts
probabilities.x
from 1 to n
:
x < k
, update windowSum
by adding dp[x]
to it.x - maxPts >= 0
, subtract dp[x - maxPts]
from windowSum
.
Consider n = 6
, k = 1
, maxPts = 10
.
k = 1
).
For a more complex example, suppose n = 21
, k = 17
, maxPts = 10
:
dp
for each score from 1 to 21, using the sliding window sum of the previous maxPts
probabilities.x ≥ k
, we stop adding to windowSum
and start accumulating the answer.dp[17]
to dp[21]
.class Solution:
def new21Game(self, n: int, k: int, maxPts: int) -> float:
if k == 0 or n >= k + maxPts - 1:
return 1.0
dp = [0.0] * (n + 1)
dp[0] = 1.0
windowSum = 1.0
result = 0.0
for i in range(1, n + 1):
dp[i] = windowSum / maxPts
if i < k:
windowSum += dp[i]
else:
result += dp[i]
if i - maxPts >= 0:
windowSum -= dp[i - maxPts]
return result
class Solution {
public:
double new21Game(int n, int k, int maxPts) {
if (k == 0 || n >= k + maxPts - 1) return 1.0;
vector<double> dp(n + 1, 0.0);
dp[0] = 1.0;
double windowSum = 1.0, result = 0.0;
for (int i = 1; i <= n; ++i) {
dp[i] = windowSum / maxPts;
if (i < k) windowSum += dp[i];
else result += dp[i];
if (i - maxPts >= 0) windowSum -= dp[i - maxPts];
}
return result;
}
};
class Solution {
public double new21Game(int n, int k, int maxPts) {
if (k == 0 || n >= k + maxPts - 1) return 1.0;
double[] dp = new double[n + 1];
dp[0] = 1.0;
double windowSum = 1.0, result = 0.0;
for (int i = 1; i <= n; i++) {
dp[i] = windowSum / maxPts;
if (i < k) windowSum += dp[i];
else result += dp[i];
if (i - maxPts >= 0) windowSum -= dp[i - maxPts];
}
return result;
}
}
var new21Game = function(n, k, maxPts) {
if (k === 0 || n >= k + maxPts - 1) return 1.0;
let dp = new Array(n + 1).fill(0.0);
dp[0] = 1.0;
let windowSum = 1.0, result = 0.0;
for (let i = 1; i <= n; i++) {
dp[i] = windowSum / maxPts;
if (i < k) windowSum += dp[i];
else result += dp[i];
if (i - maxPts >= 0) windowSum -= dp[i - maxPts];
}
return result;
};
Brute-force: The naive approach would try to simulate every possible sequence of draws, which would take O(maxPtsk) time and space—utterly infeasible for large values.
Optimized (Dynamic Programming):
dp[i]
for each i
from 1 to n
, and each computation is O(1) using the sliding window.dp
up to n
.This is efficient enough for the given constraints (n up to 104).
The New 21 Game problem is a classic example of using dynamic programming to efficiently compute probabilities in a stochastic process. By recognizing that each state depends only on a fixed window of previous states, we avoid brute-force enumeration and achieve a linear-time solution. The sliding window technique for the DP transition is the key insight, making the approach both elegant and efficient.