Alice plays the following game, loosely based on the card game "21".
Alice starts with 0
points and draws numbers while she has less than k
points. During each draw, she gains an integer number of points randomly from the range [1, maxPts]
, where maxPts
is an integer. Each draw is independent and the outcomes have equal probabilities.
Alice stops drawing numbers when she gets k
or more points.
Return the probability that Alice has n
or fewer points.
Answers within 10-5
of the actual answer are considered accepted.
Example 1:
Input: n = 10, k = 1, maxPts = 10 Output: 1.00000 Explanation: Alice gets a single card, then stops.
Example 2:
Input: n = 6, k = 1, maxPts = 10 Output: 0.60000 Explanation: Alice gets a single card, then stops. In 6 out of 10 possibilities, she is at or below 6 points.
Example 3:
Input: n = 21, k = 17, maxPts = 10 Output: 0.73278
Constraints:
0 <= k <= n <= 104
1 <= maxPts <= 104
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The brute force approach to this card game involves simulating every possible game scenario. We explore all the ways to draw cards until the game ends, either by exceeding a target score or stopping beforehand. By exhaustively checking each scenario, we determine the likelihood of winning.
Here's how the algorithm would work step-by-step:
def new_21_game_brute_force(maximum_number, end_range, maximum_card):
total_outcomes = 0
winning_outcomes = 0
def play_game(current_score):
nonlocal total_outcomes, winning_outcomes
# Increment total outcomes, because we've reached a terminal state
total_outcomes += 1
if current_score >= end_range and current_score <= maximum_number:
# We have a winning hand
winning_outcomes += 1
return
if current_score > maximum_number:
# We bust
return
# Recursively simulate next card draws to explore further scenarios
for card_value in range(1, maximum_card + 1):
play_game(current_score + card_value)
play_game(0)
if total_outcomes == 0:
return 0.0
return winning_outcomes / total_outcomes
The core idea is to calculate the winning probability directly, rather than simulating the game. We can do this using dynamic programming, working backwards from the end to the beginning.
Here's how the algorithm would work step-by-step:
def new_21_game(maximum_points, k_value, maximum_draw):
if maximum_points >= k_value + maximum_draw or k_value == 0:
return 1.0
# This array stores the win probability for each score.
win_probability = [0.0] * (maximum_points + 1)
# Initialize winning probabilities for scores >= k_value
for current_score in range(k_value, maximum_points + 1):
win_probability[current_score] = 1.0
sum_of_probabilities = sum(win_probability[k_value:])
# Calculate probabilities backward from k_value - 1 down to 0.
for current_score in range(k_value - 1, -1, -1):
win_probability[current_score] = sum_of_probabilities / maximum_draw
# Adjust rolling sum for the next iteration.
sum_of_probabilities -= win_probability[current_score + maximum_draw] if current_score + maximum_draw <= maximum_points else 0
sum_of_probabilities += win_probability[current_score]
# Return the probability of winning starting from score 0.
return win_probability[0]
Case | How to Handle |
---|---|
N, K, maxPts are all zero. | Return 1.0 as Alice starts with 0 and doesn't draw, already satisfying the win condition if K is 0. |
K is 0 and N is greater than 0 | Return 1.0 because Alice already has a winning hand. |
N is greater than or equal to K + maxPts - 1 (maximum possible score) | Alice is guaranteed to win, so return 1.0. |
maxPts is 0 and K > 0 | Alice can never draw, so if her initial score is below K, she loses, return 0.0 |
K is a large number (close to int max) but maxPts is small, can cause issues with array allocation | The dp array size depends on k and n, but since the problem constraints limit k and n to 10000, this isn't an issue. |
N is much smaller than K | The probability should be 0 because Alice needs at least K to win. |
Integer overflow when calculating window sum | The problem constraints for maxPts and K ensure that the window sum calculation won't lead to integer overflow. |
maxPts = 1 | The algorithm should still work, just the window update will be smaller. |