Problem overview
singleNumber(nums) receives a non-empty IntArray in which every value appears exactly twice, except for one value that appears once. It returns that single value.
For nums = [2, 2, 1] the answer is 1. For [4, 1, 2, 1, 2] it is 4, and for [1] it is 1. The array has up to 30,000 elements, and values range from -30,000 to 30,000, so negatives are possible. The lesson asks for O(n) time and O(1) extra space.
Approach: XOR cancels pairs
There are easy ways to find the odd one out. Sorting the array and looking for the element without a neighbour takes O(n log n). Counting with a hash map takes O(n) time but O(n) space. The space limit rules out the map, and the time limit rules out sorting.
The solution uses the bitwise XOR operation. XOR compares two numbers bit by bit and gives 1 where the bits differ and 0 where they match. Three properties matter:
- A number XOR itself is 0. Every bit matches, so
6 xor 6is 0. - A number XOR 0 is the number.
6 xor 0is 6. - Order and grouping don't matter. XOR is commutative and associative, so the numbers can be combined in any order.
Now fold the entire array into one value with XOR. Because the order doesn't matter, you can imagine the equal pairs sitting next to each other. Each pair XORs to 0, and the zeros vanish into whatever is left. Only the single number has no partner, so it is the final value.
Here is [4, 1, 2, 1, 2], starting from 0:
0 xor 4is 4.4 xor 1is 5.5 xor 2is 7.7 xor 1is 6.6 xor 2is 4.
The intermediate values look meaningless, but the end result is the single number, 4. The ones and twos have cancelled out, even though they were not next to each other.
Step by step
- Start the accumulator at 0.
var s = 0is the running XOR. Zero is the neutral value, so the first number XOR-ed in is kept unchanged. - Loop over the array.
for (num in nums)visits every element. - Fold each number in.
s = s xor numupdates the accumulator. A value seen the first time is added to it, and the second time it is cancelled out again. - Return
s. After the loop, only the value that was folded in an odd number of times is left, and that is the single one.
The solution in Kotlin
class Solution {
fun singleNumber(nums: IntArray): Int {
var s = 0
for (num in nums) {
s = s xor num
}
return s
}
}
Kotlin details worth a look:
xoris a named function. Kotlin has no^operator for bits.xoris an infix function on integer types, sos xor numis another way to writes.xor(num). Other bitwise helpers follow the same style:and,or,shlandshr.Intis 32 bits. XOR works on every bit, including the sign bit, so negative numbers need no special handling.-5 xor -5is 0, and the array[-5, 3, 3]returns-5. XOR also never overflows.var. The accumulator changes in every pass, so it must be avar.IntArray. The loop readsIntvalues straight from the array, with no boxing.- The single return is always reached. The loop has no early exit, so the function needs no fallback return. The constraints guarantee that the array is non-empty.
Complexity
Time: O(n). The loop makes one pass over the array, and each XOR takes constant time.
Space: O(1). The function keeps a single integer, however large the array is. It doesn't modify the input.
Edge cases and common mistakes
- A single element.
[1]gives0 xor 1, which is 1. - The single value is 0.
[7, 0, 7]returns 0. The function can't tell this result from "nothing found", but the constraints guarantee one single value exists, so nothing needs checking. - Negative values.
[-7, 9, -7, 9, 0]gives 0, and[-5, 3, 3]gives -5, as shown above. - The single value is not first or last. The position doesn't matter, because the cancelling works in any order.
- Using addition. Summing the array doesn't isolate the odd value;
[2, 2, 1]sums to 5, not 1. The trick relies onx xor xbeing 0. - Applying it to other counts. The method works because each other value appears an even number of times, exactly two here. If values could appear three times, three copies of one value would not cancel, and XOR alone would give the wrong answer.
Testing
[2, 2, 1]gives1.[4, 1, 2, 1, 2]gives4.[1]gives1.[-5, 3, 3]gives-5.[-7, 9, -7, 9, 0]gives0.
