Card Games & Probability โ From Bitmask DP to Expectation
โ ๏ธ Master game theory with cards, numbers, and probability
Welcome to the intersection of combinatorial game theory, dynamic programming, and probability! ๐ This article covers classic card and number games that appear in interviews at top tech companies. From "Can I Win" with bitmask DP to the probability-heavy "New 21 Game" and "Bag of Mice", you'll learn to model sequential games with state spaces ranging from 2ยฒโฐ to infinite horizons.
Card and number games are a unique breed of interview problems. They combine:
State space reasoning โ tracking which numbers/cards are used
Optimal play โ both players act to maximize their win probability
Probability theory โ randomness adds an expectation layer
DP optimization โ from bitmask memoization to sliding window probability
These problems appear at Google, Meta, Amazon, and Jane Street interviews. The "I've seen this before" advantage is REAL โ understanding the pattern transforms seemingly impossible probability puzzles into solvable DP problems.
๐ก Key Insight: Most card/number games follow the same structure: a state (remaining cards, current sum), a transition(draw a card, pick a number), and a terminal condition (sum โฅ target, no cards left). The solution is always: define DP over states, compute win probability backwards.
Problem 1
1. Can I Win โ Bitmask DP Game Tree
Problem: Two players take turns picking numbers from 1 tomaxChoosableInteger. Each number can be used at most once. The player who makes the running sum reach or exceed desiredTotal wins. Determine if the first player can force a win.
๐ฏ Real-World Analogy: Imagine a game where players pick from a set of numbered tokens on a table. Once a token is taken, it's gone forever. You're trying to reach exactly the right total. The challenge: you need to think ahead โ if I pick this token, what can my opponent do? This is the essence of game tree search with memoization.
The Challenge: 2ยฒโฐ States
With maxChoosableInteger โค 20, there are up to 2ยฒโฐ = 1,048,576 possible states. That's too many for a naive recursive search without memoization. But it's perfectly manageable with bitmask DP โ use an integer where bit i = 1 means number i has been used.
๐ Real-Time Thinking โ What Do I Know?
"OK, so maxChoosableInteger is at most 20. That's small enough for bitmask DP. Each state is defined by (mask, currentSum). If I memoize by mask, I don't even need to track currentSum separately โ I can compute it from the mask. At each state, try every unused number. If picking it makes sum โฅ desiredTotal, I win immediately. Otherwise, if my opponent LOSES from the resulting state, I win. If no winning move exists, I lose. This is a classic DP with optimal play."
Algorithm
function canIWin(maxChoosable, desiredTotal) {
// Quick check: if total sum < desiredTotal, no one can win
const sumAll = (maxChoosable * (maxChoosable + 1)) / 2;
if (sumAll < desiredTotal) return false;
const memo = new Map();
function dfs(mask, sum) {
if (memo.has(mask)) return memo.get(mask);
for (let i = 1; i <= maxChoosable; i++) {
const bit = 1 << i;
if (mask & bit) continue; // already used
// If I can win immediately, or opponent loses from next state
if (sum + i >= desiredTotal || !dfs(mask | bit, sum + i)) {
memo.set(mask, true);
return true;
}
}
memo.set(mask, false);
return false;
}
return dfs(0, 0);
}
๐ Edge Cases to Consider
Total sum < desiredTotal: Neither player can reach the target. Return false.
Max choosable โฅ desiredTotal: First player picks desiredTotal on turn 1. Instant win!
Even/odd parity trick: Some versions have a mathematical shortcut โ check if the total sum of all numbers is enough, then use symmetry.
Memoization key: Using just mask (not sum) works because sum is derivable from the mask.
โก Interview Tip: When you see "two players take turns picking from a set" and "max 20 items" โ your brain should immediately jump to bitmask DP. Say it out loud: "Since each item can be used at most once, I can track the used set with a bitmask. With N โค 20, there are at most 2ยฒโฐ states, which is feasible with memoization."
Can I Win
Two players pick numbers from 1 to maxChoosableInteger. First to reach desiredTotal wins. Can player 1 force a win?
Loading playground...
Problem 2
2. 24 Game โ Expression Tree Search
Problem: Given 4 numbers, determine if you can make 24 using addition, subtraction, multiplication, and division, using each number exactly once.
๐ Real-World Analogy: This is the classic card game where you draw 4 cards from a deck and race to combine them into 24 using basic arithmetic. The twist: you can use parentheses to change the order of operations. The computer science version: generate all possible binary expression trees with all possible operator combinations and check if any evaluates to 24.
The Expression Tree Approach
The key insight: any valid expression with 4 numbers can be represented as abinary tree where leaves are numbers and internal nodes are operators. The search space is: pick 2 numbers โ apply an operator โ reduce to 3 numbers โ repeat until 1 number remains. Check if that number equals 24.
๐ Real-Time Thinking โ What Do I Know?
"4 numbers, 4 operators, each number used exactly once. The expression can be parenthesized arbitrarily. A binary tree with 4 leaves has a fixed shape: ((a โ b) โ c) โ d or (a โ b) โ (c โ d) or (a โ (b โ c)) โ d, etc. Instead of generating all tree shapes explicitly, I can just pick any two numbers, apply all operators, and recurse on the reduced set. Division gives floating point โ I need an epsilon comparison, not exact equality."
Algorithm
function judgePoint24(nums) {
if (nums.length === 1) return Math.abs(nums[0] - 24) < 1e-6;
for (let i = 0; i < nums.length; i++) {
for (let j = 0; j < nums.length; j++) {
if (i === j) continue;
const candidates = [];
const a = nums[i], b = nums[j];
candidates.push(a + b, a * b, a - b, b - a);
if (Math.abs(a) > 1e-6) candidates.push(b / a);
if (Math.abs(b) > 1e-6) candidates.push(a / b);
const rest = nums.filter((_, k) => k !== i && k !== j);
for (const val of candidates) {
if (judgePoint24([...rest, val])) return true;
}
}
}
return false;
}
๐ Edge Cases to Consider
Floating point precision: Division produces fractions. Use epsilon = 1e-6.
Division by zero: Check |denominator| > 1e-6 before dividing.
Commutative operators: + and ร are commutative, but โ and รท are not. Include both orders.
Same numbers: If nums has duplicates, the algorithm still works โ it tries each pair by index.
Zero as a number: [0, 0, 0, 0] โ impossible. Algorithm correctly returns false.
24 Game
Given 4 numbers, determine if you can make 24 using +, -, ร, รท with each number exactly once.
Loading playground...
Problem 3
3. New 21 Game โ DP with Probability & Sliding Window
Problem: You draw points from [1, W] uniformly at random. You stop drawing when your cumulative sum โฅ K. You win if your final sum โค N. Compute the probability of winning.
๐ฐ Real-World Analogy: Think of a simplified blackjack: you draw cards (each worth 1 to W points) until you reach or exceed K. If you "bust" (go over N), you lose. The dealer isn't playing โ it's just you against probability. The question: what's your chance of ending up in the sweet spot [K, N]?
The DP Probability Distribution
This is a one-player game with randomness. We computedp[i] = probability of reaching exactly sum i. The transition: from any state i (where i < K), you draw a number from 1 to W with equal probability 1/W, moving to state i + draw.
The naive DP is O(K ร W). But we can optimize to O(N) using asliding window: dp[i] = (windowSum) / W, where windowSum = dp[i-1] + dp[i-2] + ... + dp[i-W].
๐ Real-Time Thinking โ What Do I Know?
"This is a probability DP. I need to find the probability that my final sum is between K and N. I start at sum 0 with probability 1. For each sum i < K, I can transition to i+1, i+2, ..., i+W with equal probability 1/W. So dp[i] = sum of dp[i-W]...dp[i-1] / W. That's a sliding window! I can maintain a running window sum. For i โฅ K, I stop drawing, so dp[i] accumulates into the answer if i โค N. The answer is the sum of dp[K] through dp[N]."
Algorithm
function new21Game(N, K, W) {
if (K === 0) return 1.0; // Already stopped, always win (0 โค N)
const dp = new Array(N + W + 1).fill(0);
dp[0] = 1;
let windowSum = 1; // dp[0]
let result = 0;
for (let i = 1; i <= N + W; i++) {
dp[i] = windowSum / W;
if (i >= K) {
// Terminal state โ stop drawing
if (i <= N) result += dp[i];
} else {
// Still drawing โ add to sliding window
windowSum += dp[i];
}
// Remove dp[i-W] from window (it's now out of range)
if (i - W >= 0) windowSum -= dp[i - W];
}
return result;
}
๐ Edge Cases to Consider
K = 0: Game stops before it starts. Sum = 0 โค N, so return 1.0.
N โฅ K + W - 1: You can never bust โ the maximum sum before stopping is K + W - 1. If N is large enough, you always win.
Large N vs K: If N < K, you can never win (you stop at sum โฅ K, but win needs sum โค N < K). Answer = 0.
Sliding window boundaries: Be careful with array bounds โ your DP array must go up to N + W to handle overflow states.
๐ The Sliding Window Pattern: When DP transitions are "average of the last W values", the sliding window technique turns O(KรW) into O(K). This pattern appears in many probability DP problems. Learn it once, apply it everywhere!
New 21 Game
Draw from [1, W] until sum โฅ K. Win if final sum โค N. Compute win probability.
Loading playground...
Problem 4
4. Flower Game โ Alice, Bob & Parity Strategy
Problem: Alice and Bob play a game with a row of flowers. Each flower has a number. Players take turns picking from either end of the row. The player with the higher total sum wins. Determine if Alice can win (or force a draw).
๐บ Real-World Analogy: Imagine a row of flower pots, each with a different price tag. Two collectors take turns picking a pot from either end of the row. At the end, whoever collected the most value wins. The strategy isn't just about picking the highest visible value โ it's about forcing your opponent into bad picks.
The Parity Strategy
This is a classic optimal play problem. Alice can always compute her maximum guaranteed score using DP: dp[i][j] = maximum score the current player can achieve from subarray nums[i..j].
function flowerGame(nums) {
const n = nums.length;
const dp = Array.from({ length: n }, () => new Array(n).fill(0));
// Base: single flower
for (let i = 0; i < n; i++) dp[i][i] = nums[i];
// Fill DP table: gap from 1 to n-1
for (let gap = 1; gap < n; gap++) {
for (let i = 0; i + gap < n; i++) {
const j = i + gap;
// Pick left: nums[i] + opponent's best from rest
const pickLeft = nums[i] - dp[i + 1][j];
// Pick right: nums[j] + opponent's best from rest
const pickRight = nums[j] - dp[i][j - 1];
dp[i][j] = Math.max(pickLeft, pickRight);
}
}
// If dp[0][n-1] โฅ 0, Alice wins or draws
return dp[0][n - 1] >= 0;
}
๐ Edge Cases to Consider
Odd/even length: With even length and optimal play, Alice can force a win if total sum > 0.
Draw condition: If both players end with equal totals, Alice wins? Depends on problem statement.
Single element: Alice takes it and wins (or draws if the target is 0).
All zeros: Both players end with 0. Draw (or Alice wins depending on rules).
โก The Pattern:dp[i][j] = max(nums[i] โ dp[i+1][j], nums[j] โ dp[i][jโ1]). This formula works because the opponent is also playing optimally. The difference representation (score_current โ score_opponent) elegantly handles both players' optimal play.
Flower Game
Alice and Bob pick flowers from ends of a row. Who wins with optimal play?
Loading playground...
Coin Game (Leetcode)
Two players pick coins from ends. Maximum score difference with optimal play.
Loading playground...
Vowel Game
Alice and Bob remove vowels from a string. Determine the winner.
Loading playground...
Problem 5
5. Bag of Mice โ Probability with Recursive Expectation
Problem: A bag contains w white mice andg gray mice. Alice and Bob take turns drawing one mouse each at random. Alice wins if she draws a white mouse. Bob wins if he draws a white mouse. After each pair of draws (Alice then Bob), one random mouse from the bag escapes (is removed). Determine Alice's win probability.
๐ญ Real-World Analogy: Imagine a bag with white and gray marbles. Two players alternate drawing one marble. If you draw white, you win immediately. After both players have drawn once, a random marble falls out of the bag. The game continues until someone draws white or the bag is empty. This combines probability with state evolution โ the bag composition changes each round.
The Recursive Probability Tree
At state (w, g) on Alice's turn:
Alice draws white with probability w / (w+g) โ Alice wins immediately.
Alice draws gray with probability g / (w+g) โ Bob gets a turn.
If Alice drew gray, it's Bob's turn at state (w, g-1):
Bob draws white โ Bob wins (Alice loses).
Bob draws gray โ one random mouse escapes โ state reduces โ Alice's turn again.
๐ Real-Time Thinking โ What Do I Know?
"This is a recursive probability problem. At each state (w, g), Alice's win probability P(w, g) depends on: (1) she draws white now, or (2) she draws gray AND Bob draws gray AND Alice wins from the resulting state. The escape step after each round reduces total mice by 3 (Alice draws 1, Bob draws 1, 1 escapes). The state space is bounded by w, g โค 1000, so memoization is essential."
Algorithm
function bagOfMice(w, g) {
const memo = new Map();
function dfs(white, gray) {
if (white <= 0) return 0; // No white mice left โ Alice can't win
if (gray < 0) return 0; // Invalid state
if (gray === 0) return 1; // Only white left โ Alice wins for sure
const key = white + "," + gray;
if (memo.has(key)) return memo.get(key);
const total = white + gray;
// Alice draws white immediately
let prob = white / total;
// Alice draws gray, then Bob draws, then escape
if (gray >= 2) {
// Bob draws white
const bobWhite = (gray / total) * (white / (total - 1));
// Bob draws gray, then escape
const bobGray = (gray / total) * ((gray - 1) / (total - 1));
// If Bob draws gray, one mouse escapes (could be white or gray)
// Escape white: state becomes (white-1, gray-2)
const escapeWhite = bobGray * (white / (total - 2));
// Escape gray: state becomes (white, gray-3)
const escapeGray = bobGray * ((gray - 2) / (total - 2));
prob += escapeWhite * dfs(white - 1, gray - 2);
prob += escapeGray * dfs(white, gray - 3);
}
memo.set(key, prob);
return prob;
}
return dfs(w, g);
}
๐ Edge Cases to Consider
No white mice (w = 0): Alice can never win. Return 0.
No gray mice (g = 0): Alice always draws white. Return 1.
Single mouse (w + g = 1): Alice draws it. Wins if white, loses if gray.
Gray < 2 after Alice's draw: Bob can't draw gray, so if Alice drew gray, Bob draws white โ Alice loses.
Double counting: The escape step reduces total by 3. Ensure w, g don't go negative.
๐ The Key Pattern: This problem combines immediate win probability(drawing white now) with recursive expectation (if both draw gray, the state evolves). The escape step adds complexity โ you need to consider which color mouse escapes, branching into two possible next states. The memoization key is just (white, gray) โ the total is derivable.
Bag of Mice
Alice and Bob draw mice from a bag. First to draw white wins. Compute Alice's win probability.
Loading playground...
More Problems
๐ฏ More Card & Number Game Problems
Beyond the five core problems above, here are additional card and number game problems that test similar concepts:
Coin Games
Coin Game (Leetcode): Two players pick coins from either end of a row. Each coin has a value. The player with the higher total wins. This is identical to the Flower Game pattern โ use the same dp[i][j] formula with optimal play difference scoring.
Coin Game (GFG): A variant where coins are arranged in a circle or where players can pick from any position. The DP state expands to track which coins remain, often requiring bitmask DP.
Coin Game (Leetcode)
Pick coins from ends of a row. Maximize your total with optimal play.
Loading playground...
Coin Game (GFG)
Pick coins in various arrangements. Find the winner with optimal strategy.
Loading playground...
Vowel Game
Vowel Game: Alice and Bob are given a string. They take turns removing a vowel from the string. If a player cannot make a move (no vowels left), they lose. This is a parity game โ the winner depends on whether the number of vowels is odd or even, combined with optimal play around consonants.
Vowel Game
Players remove vowels from a string. Whoever can't move loses. Determine the winner.
Loading playground...
Fun Game & Kitty-Katty
Fun Game: A number game where players alternately remove 1, 2, or 3 from a pile. The player who takes the last element wins. This is a simple Nim variant โ check if the total is divisible by 4 to determine the winner.
Kitty and Katty: Two players, Kitty and Katty, play with a sequence of numbers. Each turn, a player picks two adjacent numbers, removes them, and inserts their sum (or difference, product, etc. depending on the variant). The game reduces the sequence until one number remains. The player who makes the last move wins. This tests understanding of how the operation affects the outcome parity.
Fun Game
A subtraction game where players remove 1-3 from a pile. Last move wins.
Loading playground...
Kitty and Katty
Two players combine adjacent numbers. Last move wins. Analyze the operation's effect on win probability.
Loading playground...
Number Game (HackerRank)
A game where players pick numbers from a list. The player with the highest score at the end wins.
Loading playground...
๐ก Unified Framework: All these problems follow the same pattern:
Define the state โ what's available (coins, vowels, piles, numbers)
Define the transition โ what a player can do on their turn
Define terminal conditions โ win/lose/draw
Apply DP โ compute optimal outcome from each state
The difference scoring trick (dp[i][j] = max(nums[i] โ dp[i+1][j], nums[j] โ dp[i][jโ1])) works for ALL two-player zero-sum games with perfect information and no randomness.
Interview Prep
๐ฏ Interview Cheat Sheet
Q1: How do I know if a problem needs bitmask DP vs interval DP? Bitmask DP: each element can be used at most once, set size โค 20. Interval DP: elements form a sequence and you pick from ends (like Flower Game, Coin Game).
Q2: What's the difference between "optimal play" and "probability" games? Optimal play: both players are adversarial and make choices. Probability: outcomes are random, you compute expectation. Some problems (like Bag of Mice) combine both โ Alice chooses nothing (random draw), but the state evolves probabilistically.
Q3: When do I use difference scoring dp[i][j] = max(nums[i] โ dp[i+1][j], ...)? When you need the score difference between the current player and the opponent, assuming both play optimally. The beauty of this formula: it eliminates the need to track whose turn it is โ the DP always represents the current player's advantage.
Q4: How do I handle floating point probability in DP? Use double precision (JavaScript number). For exact rational results, store as numerator/denominator pair using BigInt. Most interview problems accept floating point with 1e-5 tolerance.
Q5: What if the state space is too large for DP? Look for mathematical shortcuts. "Can I Win" with desiredTotal > sumAll โ return false. "Fun Game" with pile size divisible by 4 โ first player loses. "Vowel Game" โ parity of vowel count. Some problems have closed-form solutions!
Q6: What's the sliding window DP trick for probability? When dp[i] = (dp[i-1] + dp[i-2] + ... + dp[i-W]) / W, maintain a running window sum instead of recomputing. Update:windowSum += dp[i] โ dp[i-W]. O(N) instead of O(NรW).
Summary
๐ Key Takeaways
Bitmask DP (Can I Win) โ 2ยฒโฐ states at most, mask tracks used items, memoize for speed
Expression Trees (24 Game) โ Pick 2 numbers, apply operator, recurse; use epsilon for division
Sliding Window DP (New 21 Game) โ Probability averages โ maintain running window sum