Problem overview
The method singleNumber receives a non-empty list of integers, nums. Every value appears exactly twice except one, which appears once. The method returns that single value.
For nums = [4, 1, 2, 1, 2] the 1 and the 2 each appear twice, and the 4 appears once, so the answer is 4. For [2, 2, 1] the answer is 1, and for [1] it is 1, because the list may hold just the single value.
The statement asks for O(n) time and O(1) extra space. That rules out sorting, which costs O(n log n), and a set or dictionary of the values seen so far, which needs O(n) memory.
Approach: let equal numbers cancel
The obvious ways to find the odd one out are to count each value with a dictionary, or to sort the list and look for a value whose neighbours differ. Both work, and both pay in time or in memory. The idea here avoids both by using the bitwise XOR operator, ^.
XOR compares two numbers bit by bit and gives 1 where the bits differ and 0 where they match. Three properties matter:
x ^ x = 0: a number XORed with itself cancels completely.x ^ 0 = x: XOR with 0 changes nothing.- The order doesn't matter, because XOR is commutative and associative.
So if you XOR all the values together, you can rearrange them mentally so that each pair sits side by side. Every pair becomes 0, the zeros disappear, and only the single value is left. For example, 4 ^ 1 ^ 2 ^ 1 ^ 2 equals 4 ^ (1 ^ 1) ^ (2 ^ 2), which is 4 ^ 0 ^ 0, which is 4.
A smaller example shows it at the bit level. 5 is 101 in binary and 3 is 011. Then 5 ^ 3 is 110, which is 6. XORing 5 again gives 110 ^ 101 = 011, which is 3: the second 5 undoes the first one.
Nothing needs to be remembered about which values were seen. One running number is enough, and that is the O(1) space.
Step by step
- Start an accumulator
sat 0. Zero is the identity for XOR, so the first value passes through unchanged. - Loop over
numswith the loop variableval, so each element is visited once. - Fold each value into
swiths = s ^ val. After the loop, every value that appeared twice has been XORed in twice and has cancelled. - Return
s. What is left is the value that was XORed in only once.
The solution in Python
class Solution:
def singleNumber(self, nums: List[int]) -> int:
s = 0
for val in nums:
s = s ^ val
return s
The Python details worth knowing:
- The operator is
^, not**. In Python,^is bitwise XOR. Exponentiation is**. Writing2 ^ 3gives 1, not 8. - Augmented form.
s ^= valdoes the same ass = s ^ val. The longer form shows the operation more clearly while you learn it. - Negative numbers work. Python integers have no fixed width and behave as if negative numbers had infinitely many leading 1 bits. XOR still cancels equal values, so
-3 ^ 5 ^ -3is 5. The constraints allow values from -30000 to 30000, and the method handles all of them. - No overflow. Python integers grow as needed, so there is nothing to wrap around. The result also always has a value that was in the list, so it fits in the same range.
- Ordinary iteration.
for val in numswalks the list from the first element to the last without any index. The list isn't changed.
Complexity
Time: O(n). The loop visits each of the n elements once and does a constant amount of work for each: one XOR on small integers.
Space: O(1). The only extra storage is the integer s and the loop variable val. It doesn't depend on the length of the list.
Edge cases and common mistakes
- A single element.
[1]gives0 ^ 1, which is 1. - The single value is 0.
[0, 5, 5]returns 0. That is correct, and the answer being 0 doesn't mean that nothing was found. - Negative values.
[-3, 5, -3]returns 5. - Starting the accumulator at 1 or at
nums[0]with a loop over everything. Starting at 1 pollutes the result with an extra XOR of 1. Starting atnums[0]and then looping over the whole list XORs the first value in twice, which cancels it. Start at 0, or start atnums[0]and loop over the rest. - Using addition or subtraction. Adding the values doesn't cancel pairs. XOR does, because each bit is its own opposite.
- Relying on the input rule. The method assumes every other value appears exactly twice. If a value appeared three times, it wouldn't cancel. For example,
[2, 2, 2, 1]returns 3, not 1, which is the XOR of the 2 left over with the 1. This lesson's tests only use lists that follow the rule.
Testing
nums = [2, 2, 1]gives1.nums = [4, 1, 2, 1, 2]gives4.nums = [1]gives1.nums = [-3, 5, -3]gives5.
