title: Chalkboard XOR Game slug: chalkboard-xor-game difficulty: hard leetcode_id: 810 leetcode_url: https://leetcode.com/problems/chalkboard-xor-game/ categories: - arrays - math patterns: - greedy function_signature: "def xor_game(nums: list[int]) -> bool:" test_cases: visible: - input: { nums: [1, 1, 2] } expected: false - input: { nums: [0, 1] } expected: true - input: { nums: [1, 2, 3] } expected: true hidden: - input: { nums: [1] } expected: false - input: { nums: [0, 0] } expected: true - input: { nums: [1, 1] } expected: true - input: { nums: [1, 2, 3, 4] } expected: true - input: { nums: [5, 5, 5] } expected: false - input: { nums: [1, 2, 4, 8] } expected: true description: | You are given an array of integers `nums` representing the numbers written on a chalkboard. Alice and Bob take turns erasing exactly one number from the chalkboard, with Alice starting first. If erasing a number causes the bitwise XOR of all the elements of the chalkboard to become `0`, then that player loses. The bitwise XOR of one element is that element itself, and the bitwise XOR of no elements is `0`. Also, if any player starts their turn with the bitwise XOR of all the elements of the chalkboard equal to `0`, then that player wins. Return `true` *if and only if Alice wins the game, assuming both players play optimally*. constraints: | - `1 <= nums.length <= 1000` - `0 <= nums[i] < 2^16` examples: - input: "nums = [1,1,2]" output: "false" explanation: "Alice has two choices: erase 1 or erase 2. If she erases 1, nums becomes [1, 2] with XOR = 3. Bob can then force Alice to erase the last element and lose. If Alice erases 2, nums becomes [1, 1] with XOR = 0, so Alice loses immediately." - input: "nums = [0,1]" output: "true" explanation: "Alice can erase 1, leaving [0]. The XOR is 0, but it was already non-zero before her move completed, and now Bob faces XOR = 0 at the start of his turn, so Bob loses." - input: "nums = [1,2,3]" output: "true" explanation: "XOR of [1,2,3] = 0. Alice starts with XOR = 0, so she wins immediately." explanation: intuition: | This problem looks like it requires game simulation or minimax, but there's a beautiful mathematical insight that reduces it to a simple formula. Think about XOR properties: if the XOR of all elements is already `0` when it's your turn, you win. The key insight is understanding *when* Alice can be forced into a losing position. Alice loses only if **every** number she could erase would make the XOR become `0`. Let's think about when this happens. If the current XOR is `X ≠ 0`, and Alice must pick a number `nums[i]` such that `X XOR nums[i] = 0`, then `nums[i] = X`. This means Alice loses only if **all** remaining numbers equal the current XOR value — which means all numbers are identical. But here's the clever part: if all `n` numbers are identical (say, all equal to `v`), then: - If `n` is even: XOR = `v XOR v XOR ... = 0`, so Alice wins immediately - If `n` is odd: XOR = `v`, and Alice must erase `v`, leaving an even count where Bob faces XOR = 0 and wins Generalising this: Alice loses only when forced to make XOR = 0. With an even number of elements and non-zero XOR, Alice can always find a "safe" move (at least one element whose removal doesn't make XOR = 0). This is because if removing **any** element made XOR = 0, all elements would need to equal the current XOR, making them all identical — but then with even count, XOR would already be 0. Therefore: **Alice wins if and only if the initial XOR is 0, OR the array has an even length.** approach: | The solution is remarkably simple once you understand the game theory: **Step 1: Calculate the XOR of all elements** - Compute `xor_sum` by XORing all elements in `nums` - This tells us the starting game state   **Step 2: Check the two winning conditions for Alice** - **Condition 1:** If `xor_sum == 0`, Alice wins immediately (she starts with XOR = 0) - **Condition 2:** If `len(nums)` is even, Alice wins (she can always find a safe move)   **Step 3: Return the result** - Return `True` if either condition is met, `False` otherwise - This covers all cases: Alice wins if she starts with XOR = 0, or if she has an even number of elements to work with   The mathematical proof: with even length and non-zero XOR, if removing any element made XOR = 0, then each element `nums[i]` would equal the total XOR. But `n` identical values XORed together (with `n` even) gives 0, contradicting our assumption that XOR ≠ 0. common_pitfalls: - title: Attempting Game Simulation description: | A natural first instinct is to simulate the game using recursion or dynamic programming, trying all possible moves for both players. With up to 1000 elements, this approach has exponential complexity — the game tree branches at each turn, leading to `O(n!)` possibilities. This will cause **Time Limit Exceeded**. The key is recognising this as a **mathematical problem** with a closed-form solution, not a search problem. wrong_approach: "Recursive simulation with memoisation" correct_approach: "Use XOR properties and parity analysis" - title: Forgetting the Even-Length Guarantee description: | Some solutions correctly check if XOR = 0 but miss the even-length winning condition. The insight is subtle: with an even number of elements and non-zero XOR, Alice can *always* find at least one element whose removal doesn't make XOR = 0. This is guaranteed by the pigeonhole principle applied to XOR values. wrong_approach: "Only checking if initial XOR is 0" correct_approach: "Check both XOR = 0 and even length conditions" - title: Misunderstanding the Winning Condition description: | The rules have a subtle distinction: - If you **start** your turn with XOR = 0, you **win** - If your move **causes** XOR to become 0, you **lose** These are different! Starting with XOR = 0 is an immediate win, not a loss. This catches players who think XOR = 0 is always bad. wrong_approach: "Thinking XOR = 0 means current player loses" correct_approach: "XOR = 0 at turn start means win; causing XOR = 0 means loss" key_takeaways: - "**Game theory simplification**: Many two-player games have elegant mathematical solutions — look for invariants and parity arguments before attempting simulation" - "**XOR properties**: XOR of identical values cancels out (even count = 0), which drives the parity-based solution" - "**Constraint analysis**: The even-length guarantee comes from a proof by contradiction using XOR algebra" - "**Pattern recognition**: This problem teaches that 'hard' brainteasers often have O(n) or O(1) solutions hiding behind the complexity" time_complexity: "O(n). We traverse the array once to compute the XOR of all elements." space_complexity: "O(1). We only use a single variable (`xor_sum`) regardless of input size." solutions: - approach_name: XOR and Parity Check is_optimal: true code: | def xor_game(nums: list[int]) -> bool: # Calculate XOR of all elements xor_sum = 0 for num in nums: xor_sum ^= num # Alice wins if: # 1. XOR is already 0 (she wins immediately), OR # 2. Array has even length (she can always find a safe move) return xor_sum == 0 or len(nums) % 2 == 0 explanation: | **Time Complexity:** O(n) — Single pass to compute XOR. **Space Complexity:** O(1) — Only one integer variable used. The solution uses game theory insight: Alice wins if she starts with XOR = 0, or if she has an even number of elements (guaranteeing a safe move exists). This elegant formula avoids expensive game tree simulation. - approach_name: One-Liner with Reduce is_optimal: true code: | from functools import reduce from operator import xor def xor_game(nums: list[int]) -> bool: # XOR all elements; Alice wins if result is 0 or length is even return reduce(xor, nums) == 0 or len(nums) % 2 == 0 explanation: | **Time Complexity:** O(n) — Reduce iterates through all elements. **Space Complexity:** O(1) — No additional space beyond the accumulator. A more Pythonic version using `functools.reduce` with the XOR operator. Functionally identical to the explicit loop but more concise.