Facebook Pixel

292. Nim Game

EasyBrainteaserMathGame Theory
Leetcode Link

Problem Description

This is a two-player game called Nim Game where you and your friend take turns removing stones from a heap.

The game rules are:

  • There is initially a heap containing n stones on the table
  • You always go first
  • Players alternate turns
  • On each turn, a player must remove 1, 2, or 3 stones from the heap
  • The player who removes the last stone wins the game

Given an integer n representing the initial number of stones, you need to determine if you can win the game when both players play optimally (meaning both players make the best possible moves).

The solution reveals an interesting pattern: you can win if and only if n % 4 != 0.

The key insight is that positions where the number of stones is divisible by 4 are losing positions for the current player. Here's why:

  • If n < 4 (i.e., n = 1, 2, or 3), you can take all stones and win immediately
  • If n = 4, no matter how many stones you take (1, 2, or 3), your opponent can take the remaining stones and win
  • If n = 5, 6, or 7, you can take 1, 2, or 3 stones respectively to leave exactly 4 stones for your opponent, putting them in a losing position
  • If n = 8, any move you make (taking 1, 2, or 3 stones) leaves 7, 6, or 5 stones for your opponent, all winning positions for them

This pattern continues: multiples of 4 are always losing positions because any legal move leaves the opponent with a non-multiple of 4 (a winning position), while non-multiples of 4 are winning positions because you can always force your opponent into a multiple of 4 (a losing position).

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key to solving this problem is recognizing that it's a classic game theory problem where we need to identify winning and losing positions.

Let's think about this step by step by analyzing small values of n:

  • When n = 1, 2, or 3: You can take all stones in one move and win immediately. These are winning positions.
  • When n = 4: This is interesting - no matter what you do (take 1, 2, or 3 stones), you leave your opponent with 3, 2, or 1 stones respectively. Your opponent can then take all remaining stones and win. So n = 4 is a losing position.

Now let's continue:

  • When n = 5: You can take 1 stone, leaving 4 for your opponent (forcing them into the losing position we just identified)
  • When n = 6: You can take 2 stones, leaving 4 for your opponent
  • When n = 7: You can take 3 stones, leaving 4 for your opponent

So positions 5, 6, and 7 are all winning positions because you can force your opponent into the losing position of 4.

What about n = 8? No matter what move you make:

  • Take 1 stone → opponent gets 7 (winning position for them)
  • Take 2 stones → opponent gets 6 (winning position for them)
  • Take 3 stones → opponent gets 5 (winning position for them)

So n = 8 is another losing position!

The pattern emerges: positions that are multiples of 4 (4, 8, 12, ...) are losing positions, while all other positions are winning positions. This happens because:

  1. From any multiple of 4, you can only move to a non-multiple of 4 (since you can only take 1, 2, or 3 stones)
  2. From any non-multiple of 4, you can always move to a multiple of 4 (by taking the remainder when divided by 4)

Therefore, the solution is simply checking if n % 4 != 0. If the remainder is not zero, you're in a winning position and can force your opponent into a losing position (multiple of 4). If the remainder is zero, you're already in a losing position and any move you make gives your opponent a winning position.

Solution Approach

Based on the pattern we discovered, the implementation is remarkably simple and elegant.

The winning strategy revolves around the mathematical relationship with the number 4. From our analysis:

  1. Losing positions: When n is divisible by 4 (i.e., n = 4, 8, 12, 16, ...)
  2. Winning positions: When n is not divisible by 4 (i.e., n = 1, 2, 3, 5, 6, 7, 9, 10, 11, ...)

The implementation uses the modulo operator to check this condition:

class Solution:
    def canWinNim(self, n: int) -> bool:
        return n % 4 != 0

Why this works:

  • If n % 4 = 0: You're at a losing position. Any move you make (removing 1, 2, or 3 stones) will leave n-1, n-2, or n-3 stones, all of which have non-zero remainders when divided by 4. This puts your opponent in a winning position.

  • If n % 4 = 1, 2, or 3: You're at a winning position. You can remove exactly n % 4 stones, leaving a multiple of 4 for your opponent, forcing them into a losing position.

Mathematical proof by induction:

  • Base case: For n < 4, the first player wins by taking all stones
  • When n = 4k (multiple of 4): Any legal move leaves 4k-1, 4k-2, or 4k-3 stones, none of which are divisible by 4, giving the opponent a winning position
  • When n = 4k + r (where r = 1, 2, or 3): The first player can remove r stones, leaving exactly 4k stones for the opponent, forcing them into a losing position

The time complexity is O(1) since we only perform a single modulo operation, and the space complexity is also O(1) as we don't use any additional data structures.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with n = 10 stones to illustrate the solution approach.

Initial Setup: 10 stones on the table, you go first.

Step 1: Check if you can win

  • Calculate 10 % 4 = 2
  • Since the remainder is not 0, you're in a winning position

Step 2: Find the optimal move

  • Since 10 % 4 = 2, you should remove 2 stones
  • This leaves exactly 8 stones for your opponent

Step 3: Opponent's turn (8 stones)

  • Your opponent now faces 8 stones
  • 8 % 4 = 0, so they're in a losing position
  • No matter what they do:
    • Remove 1 stone → 7 stones remain
    • Remove 2 stones → 6 stones remain
    • Remove 3 stones → 5 stones remain
  • All these outcomes (7, 6, or 5) have non-zero remainders when divided by 4

Step 4: Your turn again (let's say opponent removed 3, leaving 5 stones)

  • You face 5 stones
  • 5 % 4 = 1, so you're in a winning position
  • Remove 1 stone, leaving 4 for your opponent

Step 5: Opponent's turn (4 stones)

  • Your opponent faces 4 stones (a losing position again)
  • Whatever they remove (1, 2, or 3), you can take the remaining stones and win

Game conclusion:

  • If opponent removes 1 → 3 stones left → you take all 3 and win
  • If opponent removes 2 → 2 stones left → you take both and win
  • If opponent removes 3 → 1 stone left → you take it and win

This example demonstrates how maintaining the pattern of leaving multiples of 4 for your opponent guarantees your victory when both players play optimally.

Solution Implementation

1class Solution:
2    def canWinNim(self, n: int) -> bool:
3        """
4        Determine if the first player can win the Nim game with n stones.
5      
6        Game rules:
7        - Two players take turns removing 1-3 stones
8        - The player who removes the last stone wins
9        - Both players play optimally
10      
11        Strategy explanation:
12        - If n is divisible by 4, the first player will lose
13        - Otherwise, the first player can always win
14      
15        This is because:
16        - With 1, 2, or 3 stones: first player wins by taking all
17        - With 4 stones: first player loses (any move leaves 1-3 for opponent to win)
18        - With 5, 6, or 7 stones: first player can leave exactly 4 for opponent
19        - This pattern repeats every 4 stones
20      
21        Args:
22            n: Number of stones in the pile
23          
24        Returns:
25            True if the first player can guarantee a win, False otherwise
26        """
27        # First player wins if and only if n is not divisible by 4
28        return n % 4 != 0
29
1class Solution {
2    /**
3     * Determines if the first player can win the Nim game.
4     * 
5     * In the Nim game with stones, players take turns removing 1-3 stones.
6     * The player who removes the last stone wins.
7     * 
8     * Game theory analysis:
9     * - If n = 1, 2, or 3: First player wins by taking all stones
10     * - If n = 4: First player loses (any move leaves 1-3 stones for opponent to win)
11     * - If n = 5, 6, or 7: First player wins by leaving exactly 4 stones for opponent
12     * - Pattern continues: First player loses only when n is divisible by 4
13     * 
14     * @param n The number of stones in the pile
15     * @return true if the first player can win with optimal play, false otherwise
16     */
17    public boolean canWinNim(int n) {
18        // First player can win if and only if n is not divisible by 4
19        return n % 4 != 0;
20    }
21}
22
1class Solution {
2public:
3    /**
4     * Determines if the current player can win the Nim game
5     * 
6     * Game rules: Players take turns removing 1-3 stones from a pile
7     * The player who removes the last stone wins
8     * 
9     * Strategy: The current player loses if and only if the number of stones
10     * is divisible by 4. This is because:
11     * - If n % 4 == 0, whatever the current player takes (1-3 stones),
12     *   the opponent can always take enough to make the total removed equal to 4
13     * - This pattern continues, ensuring the opponent takes the last stone
14     * - If n % 4 != 0, the current player can take (n % 4) stones,
15     *   leaving a multiple of 4 for the opponent, putting them in a losing position
16     * 
17     * @param n The initial number of stones in the pile
18     * @return true if the current player can win with optimal play, false otherwise
19     */
20    bool canWinNim(int n) {
21        // Current player wins if n is not divisible by 4
22        return n % 4 != 0;
23    }
24};
25
1/**
2 * Determines if the current player can win the Nim game
3 * 
4 * In the Nim game with the rule of removing 1-3 stones per turn,
5 * the player who takes the last stone wins.
6 * 
7 * Key insight: If there are 4 stones, the current player will lose
8 * because no matter how many they take (1-3), the opponent can take
9 * the remaining stones. This pattern repeats for all multiples of 4.
10 * 
11 * @param n - The initial number of stones in the heap
12 * @returns true if the current player can win with optimal play, false otherwise
13 */
14function canWinNim(n: number): boolean {
15    // Player loses if and only if the number of stones is divisible by 4
16    return n % 4 !== 0;
17}
18

Time and Space Complexity

The time complexity is O(1) because the solution only performs a single modulo operation (n % 4) and a comparison operation (!= 0), both of which take constant time regardless of the input size n. The modulo operation on integers is a constant-time arithmetic operation.

The space complexity is O(1) because the solution uses no additional data structures or variables that scale with the input. It only stores the result of the modulo operation temporarily and returns a boolean value, which requires a fixed amount of memory regardless of the value of n.

Common Pitfalls

1. Overthinking the Solution

Many developers initially try to implement complex recursive or dynamic programming solutions with memoization, attempting to simulate all possible game states. This leads to unnecessary complexity and potential stack overflow or time limit exceeded errors for large values of n.

Incorrect approach:

class Solution:
    def canWinNim(self, n: int) -> bool:
        # Overly complex recursive solution
        memo = {}
        def canWin(stones):
            if stones <= 3:
                return True
            if stones in memo:
                return memo[stones]
          
            # Check if any move leads to opponent losing
            for move in [1, 2, 3]:
                if not canWin(stones - move):
                    memo[stones] = True
                    return True
          
            memo[stones] = False
            return False
      
        return canWin(n)

Why it's problematic: This recursive solution has O(n) time complexity and O(n) space complexity, and will fail for large inputs due to recursion depth limits or memory constraints.

2. Misunderstanding the Modulo Operation

Some might incorrectly implement the condition as n % 4 == 0 (returning True when divisible by 4) instead of n % 4 != 0, reversing the win/lose logic.

Incorrect implementation:

class Solution:
    def canWinNim(self, n: int) -> bool:
        return n % 4 == 0  # Wrong! This returns True for losing positions

3. Not Recognizing the Mathematical Pattern

Developers might try to handle special cases separately without recognizing the underlying pattern, leading to verbose and error-prone code:

Inefficient approach:

class Solution:
    def canWinNim(self, n: int) -> bool:
        if n <= 3:
            return True
        if n == 4:
            return False
        if n == 5 or n == 6 or n == 7:
            return True
        if n == 8:
            return False
        # Attempting to list all cases...

4. Assuming Linear Game Strategy

Some might think that taking the maximum number of stones (3) each turn is optimal, or that there's a greedy strategy based on the current stone count, missing the deeper mathematical insight.

Solution to Avoid Pitfalls:

  1. Recognize that this is a classic game theory problem with a mathematical solution
  2. Test the pattern with small values (n = 1 to 12) to verify the modulo 4 relationship
  3. Understand that positions divisible by 4 are "cold" positions (losing for the current player)
  4. Remember that optimal play means forcing your opponent into a losing position whenever possible
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How does quick sort divide the problem into subproblems?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More