Problem overview
The function singleNumber receives a non-empty vector nums in which every value appears twice except one, and it returns that one value. For [4,1,2,1,2] the answer is 4, for [2,2,1] it is 1, and for [1] it is 1.
The statement asks for O(n) time and O(1) extra space. That rules out sorting, which costs O(n log n), and counting with a hash map or a set, which needs O(n) extra space.
Approach: equal numbers cancel under XOR
The bitwise XOR operator ^ compares two numbers bit by bit and gives 1 where the bits differ. Three of its properties solve this problem:
x ^ xis 0: a number cancels itself.x ^ 0isx: zero changes nothing.- The order doesn't matter. XOR is commutative and associative, so the values can be combined in any grouping.
Combine all the elements with XOR. The two copies of every repeated value meet somewhere in the chain and cancel to 0, wherever they sit in the vector. Only the single value remains, combined with zeros. For 4 ^ 1 ^ 2 ^ 1 ^ 2 the two 1s cancel, the two 2s cancel, and 4 is left.
The total is kept in one int, and the vector is read once.
Step by step
- Declare
sas 0. Zero is the neutral value for XOR, so the first element is combined with a value that doesn't change it. - Loop over
numswith a range-basedforand a loop variableval. - Combine each value into the total:
s = s ^ val. - Return
safter the loop.
The solution in C++
class Solution {
public:
int singleNumber(vector<int>& nums) {
int s = 0;
for (int val: nums) {
s = s ^ val;
}
return s;
}
};
A few C++ details:
^is XOR here. In C++ the caret is not a power operator.2 ^ 3is 1, not 8.- Compound form.
s ^= valdoes the same thing ass = s ^ val. - Negative values work. XOR operates on the bit patterns, and equal values have equal patterns, so they cancel for negative numbers as well. For
[-5, 7, -5]the function returns 7, and for[-1, -1, -30000]it returns -30000. - No overflow. XOR never carries, and the result of two
intoperands is anintwhose bits come straight from the operands, so nothing can overflow. - Copy or reference. The loop variable
int valcopies each element, which is cheap for anint. The vector is passed by reference, so the call doesn't copy it.
Complexity
Time: O(n). One pass over the vector, with one XOR per element.
Space: O(1). The only extra storage is the integer s.
Edge cases and common mistakes
- A single element.
[1]gives0 ^ 1, which is 1. - Starting
sat something other than 0. A non-zero start leaves that value mixed into the answer. - Using
+or-instead of^. Addition doesn't cancel pairs:2 + 2 + 1is 5, not 1. - Relying on the rule for other counts. The cancellation works because the repeated values appear exactly twice. A value that appears three times would leave itself behind, and the result would no longer be the single value.
Testing
[2,2,1]gives 1.[4,1,2,1,2]gives 4.[1]gives 1.[-5,7,-5]gives 7.
