A math and bit manipulation solution for deciding whether Alice wins the XOR removal game.
Problem Restatement
We are given an array nums of non-negative integers written on a chalkboard.
Alice and Bob take turns erasing exactly one number. Alice moves first.
After a player erases a number, if the XOR of all remaining numbers becomes 0, that player loses.
There is one special rule: if a player starts their turn and the XOR of all numbers is already 0, that player wins immediately.
Return True if Alice wins with optimal play. Otherwise, return False. The official constraints include 1 <= nums.length <= 1000 and 0 <= nums[i] <= 2^16.
Input and Output
| Item | Meaning |
|---|---|
| Input | An array nums of non-negative integers |
| Output | True if Alice wins, otherwise False |
| Move | Erase exactly one number |
| Losing condition | A player loses if their move makes the remaining XOR equal 0 |
| Immediate win | A player wins if their turn starts with XOR already equal 0 |
Examples
Example 1:
nums = [1, 1, 2]The total XOR is:
1 ^ 1 ^ 2 = 2Alice has two meaningful choices.
If Alice erases 2, the remaining numbers are:
[1, 1]Their XOR is:
1 ^ 1 = 0So Alice loses immediately.
If Alice erases 1, the remaining numbers are:
[1, 2]Their XOR is not 0, so Bob moves next. Bob can then force Alice to erase the last number and lose.
So the answer is:
FalseExample 2:
nums = [0, 1]The array length is even, so Alice has a winning strategy.
The answer is:
TrueExample 3:
nums = [1, 2, 3]The total XOR is:
1 ^ 2 ^ 3 = 0Alice starts her turn with total XOR already equal to 0, so she wins immediately.
The answer is:
TrueFirst Thought: Game Search
A direct game solution would try every possible number Alice can erase, then every possible number Bob can erase, and so on.
This creates a game tree.
At each step, there may be many choices, so the number of possible games grows very quickly. With up to 1000 numbers, this is impossible.
We need to use the structure of XOR and the parity of the number of elements.
Key Insight
Let:
x = nums[0] ^ nums[1] ^ ... ^ nums[n - 1]There are two simple winning cases for Alice:
x == 0or:
len(nums) % 2 == 0So the final answer is:
x == 0 or len(nums) % 2 == 0The first case follows directly from the rule: if Alice starts with XOR 0, Alice wins immediately.
The second case is the main observation. If the number of elements is even and the total XOR is not 0, Alice can always make a move that does not immediately lose. After Alice removes one number, Bob faces an odd number of elements. With optimal play, the parity structure favors Alice.
If the number of elements is odd and total XOR is not 0, Alice loses under optimal play.
Why Even Length Wins
Assume the current array length is even and total XOR is not 0.
Suppose every possible move made the remaining XOR equal to 0.
If Alice removes nums[i], the remaining XOR is:
x ^ nums[i]For that to be 0, we would need:
nums[i] == xfor every index i.
That means every number would equal x.
But there are an even number of elements. XORing the same value an even number of times gives 0, contradicting the assumption that the total XOR is not 0.
Therefore, at least one move does not lose immediately.
This argument is the reason even-length non-zero-XOR positions are winning.
Algorithm
Compute the XOR of all numbers:
total = 0
for num in nums:
total ^= numThen return:
total == 0 or len(nums) % 2 == 0Correctness
If total == 0, Alice wins immediately by the problem rule, so the algorithm correctly returns True.
Now suppose total != 0.
If len(nums) is even, the argument above shows Alice has at least one move that does not immediately lose. After Alice makes such a move, Bob receives an odd-length position. The same parity argument implies Bob cannot avoid eventually giving Alice a winning position. Thus Alice wins, so the algorithm correctly returns True.
If len(nums) is odd and total != 0, Alice must move to an even-length position. If her move makes the remaining XOR 0, she loses immediately. Otherwise, Bob receives an even-length non-zero-XOR position, which is winning for the player to move. Therefore Bob wins, and Alice loses. The algorithm correctly returns False.
Thus the algorithm returns True exactly when Alice has a winning strategy.
Complexity
Let n = len(nums).
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | We XOR every number once |
| Space | O(1) | We store only the running XOR |
Implementation
class Solution:
def xorGame(self, nums: list[int]) -> bool:
total = 0
for num in nums:
total ^= num
return total == 0 or len(nums) % 2 == 0Code Explanation
The variable total stores the XOR of all numbers:
total = 0XOR with 0 leaves a number unchanged, so this is the correct starting value.
We update it with every number:
for num in nums:
total ^= numAfter the loop, total is the XOR of the whole chalkboard.
Then we apply the theorem:
return total == 0 or len(nums) % 2 == 0Alice wins if the starting XOR is already 0, or if the number of elements is even.
Testing
def run_tests():
s = Solution()
assert s.xorGame([1, 1, 2]) is False
assert s.xorGame([0, 1]) is True
assert s.xorGame([1, 2, 3]) is True
assert s.xorGame([1]) is False
assert s.xorGame([0]) is True
assert s.xorGame([5, 5]) is True
print("all tests passed")
run_tests()| Test | Why |
|---|---|
[1, 1, 2] | Official losing example |
[0, 1] | Even length wins |
[1, 2, 3] | Initial XOR is 0 |
[1] | Odd length and non-zero XOR loses |
[0] | Initial XOR is 0 |
[5, 5] | Initial XOR is 0 and length is even |