Problem overview
The function single_number takes a non-empty vector of integers, nums. Every value appears exactly twice except one, which appears once. The function returns that single value.
For nums = [4, 1, 2, 1, 2] the answer is 4: the 1s and the 2s come in pairs, and 4 has no partner. For [1] the answer is 1, and for [2, 2, 1] it is 1.
The task asks for O(n) time and O(1) extra space. A hash map of counts or a sorted copy would work, but the map uses O(n) space and sorting costs O(n log n). Neither fits the budget.
Approach: XOR cancels equal values
The bitwise XOR operator, written ^ in Rust, compares two numbers bit by bit and gives a 1 wherever the bits differ. Three of its properties solve this problem:
x ^ x = 0: a value XORed with itself cancels out.x ^ 0 = x: XOR with zero changes nothing.- XOR is commutative and associative, so the order of the operands doesn't matter.
Because of the last property, XORing the whole vector gives the same result as regrouping the values so that equal ones sit next to each other. Each pair becomes 0, the zeros vanish, and only the single value is left. For the example: 4 ^ 1 ^ 2 ^ 1 ^ 2 equals 4 ^ (1 ^ 1) ^ (2 ^ 2), which is 4 ^ 0 ^ 0, which is 4.
The same works with bits. XORing 6 (00000110) and 3 (00000011) gives 00000101, which is 5. XORing 5 with 3 again gives back 6, so a value that appears twice leaves no trace.
Step by step
- Declare an accumulator.
sis a mutablei32that starts at 0, the value that changes nothing under XOR. - Loop over the vector with
for val in nums, one value at a time. - Fold each value into the accumulator with
s = s ^ val. After the loop, every pair has cancelled itself. - Return
s.
The solution in Rust
impl Solution {
fn single_number(nums: Vec<i32>) -> i32 {
let mut s: i32 = 0;
for val in nums {
s = s ^ val;
}
return s;
}
}
A few Rust details:
for val in numsconsumes the vector. The loop movesnumsinto the iterator, andvalis an ownedi32, not a reference. That is fine here because the function doesn't usenumsafterwards. Looping over&numswould give&i32values, ands ^ valstill works on them, because Rust implements^for references to integers too.- Negative numbers. Negative values are stored in two's complement, and XOR works on all 32 bits, so the trick doesn't care about signs.
[-3, 5, -3]returns 5. - No overflow. XOR only combines bits and never carries, so the result always fits in
i32, unlike a running sum. s = s ^ valcan be writtens ^= val. Both do the same thing.return s;is valid at the end of a function. Leaving off bothreturnand the semicolon, with justs, is the more usual Rust style.
Complexity
Time: O(n). The loop visits each element once and XOR on an i32 is a single operation.
Space: O(1). The only extra storage is the variable s. The vector is the input, and the loop doesn't copy it.
Edge cases and common mistakes
- A single element.
[1]gives0 ^ 1, which is 1. - Starting the accumulator at a non-zero value. The accumulator must start at 0. Any other start value would stay in the result.
- Using
+or-instead of^. Adding the values gives their total, which says nothing about which one is single. - Using
&or|. These don't cancel equal values. Only XOR hasx ^ x = 0. - Relying on the pairing rule. The method needs every other value to appear exactly twice. In
[7, 7, 7]the result is 7 whether or not that is meaningful, because three copies leave one behind. With other counts the result would not identify a single value.
Testing
nums = [2, 2, 1]gives 1.nums = [4, 1, 2, 1, 2]gives 4.nums = [1]gives 1.nums = [-3, 5, -3]gives 5.
