Problem overview
The method SingleNumber receives a non-empty int[] named nums. Every value appears exactly twice, except one that appears once. The method returns that value, in O(n) time and O(1) extra space.
For [2, 2, 1] the answer is 1. For [4, 1, 2, 1, 2] it is 4. For [1] it is 1. Values range from -30,000 to 30,000, and the array has up to 30,000 items.
Approach: XOR cancels pairs
The obvious solutions break the space limit. A HashSet or Dictionary of values seen so far uses O(n) memory, and sorting the array and scanning for the item without a neighbour takes O(n log n) time. The bitwise XOR operator, ^, does the job with one variable.
XOR compares two numbers bit by bit and gives 1 where the bits differ. Three properties matter:
x ^ x == 0: a number cancels itself.x ^ 0 == x: XOR with 0 changes nothing.- The order doesn't matter, so
a ^ b ^ aequalsa ^ a ^ b.
XOR all the items together. Each value that appears twice cancels to 0, wherever the two copies sit in the array, and what remains is the single value XOR-ed with 0, which is the value itself. For [4, 1, 2, 1, 2] the computation is 4 ^ 1 ^ 2 ^ 1 ^ 2, which equals 4.
Step by step
- Declare
int s = 0;. It is the running XOR. Starting from 0 is safe because XOR with 0 leaves any value unchanged. - Loop over
numswithforeach (int val in nums). - Fold each value in with
s = s ^ val;. Equal values meet at some point and cancel, no matter how far apart they are. - Return
s. Everything that appeared twice has cancelled, sosis the single number.
The solution in C#
class Solution {
public int SingleNumber(int[] nums) {
int s = 0;
foreach (int val in nums) {
s = s ^ val;
}
return s;
}
}
The C# details worth knowing:
^is XOR, not a power. C# has no power operator.2 ^ 3is 1, because10XOR11in binary is01.- Negative numbers work. An
intis stored in 32 bits using two's complement, and XOR works on all 32 bits.-3 ^ -3is 0, and[-1, 5, 5]returns-1. The code doesn't need any special case for signs. - The same operation, shorter.
s ^= val;means exactlys = s ^ val;. Forintvalues both forms compile. - No overflow. The XOR of two
intvalues is always a validint, so unlike addition it can't overflow.
Complexity
Time: O(n). The loop visits each item once, and one XOR costs constant time.
Space: O(1). The only extra memory is the variable s and the loop variable.
Edge cases and common mistakes
- One item.
[1]gives0 ^ 1, which is1. - The single number is negative or 0.
[-7, -7, -30000]returns-30000. A single0works too: the XOR of the pairs is 0, and0 ^ 0is 0. - Values that appear three times. XOR cancels pairs only. For
[2, 2, 2, 1], the three 2s leave one 2 behind, and the method returns3. The trick relies on the guarantee that every other value appears exactly twice. - Starting at
nums[0]. That is fine only if the loop then skips index 0. Folding the first item in twice cancels it and returns the wrong result. - Using
+. Adding the items doesn't cancel anything. XOR is what makes equal values disappear.
Testing
[2, 2, 1]gives1.[4, 1, 2, 1, 2]gives4.[1]gives1.[-1, 5, 5]gives-1.[-7, -7, -30000]gives-30000.
