Topics › Bit Manipulation › Interview Problems
Single Number - III
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given an integer array nums in which every value appears exactly twice, except for two distinct values that each appear exactly once. Return those two values. The judge accepts them in any order; the solutions below return them in increasing order so that results are easy to compare. The follow-up, as with the other Single Number problems, is to do it in linear time and constant extra space.
Constraints: 2 ≤ n ≤ 3 × 10^4, n is even, values fit in a 32-bit signed integer (negatives allowed). Exactly two values occur once, all others occur exactly twice.
Example 1
nums = [1, 2, 1, 3, 2, 5]
answer = [3, 5] // 1 and 2 are paired; 3 and 5 are alone
Example 2
nums = [-1, 0]
answer = [-1, 0] // the smallest valid input: both elements are single
Example 3
nums = [8, 24, 100, 100]
answer = [8, 24]
Approach 1: Brute force
Intuition. Count occurrences directly. For each element, scan the array and tally its value; collect the elements whose tally is 1. Stop as soon as both have been found. This is a verbatim implementation of the definition and costs a full scan per element.
Algorithm.
- For each index
i, while fewer than two singles have been found: - Count how many indices
jsatisfynums[j] == nums[i]. - If the count is
1, recordnums[i]. - Sort the two recorded values and return them.
static int[] bruteForce(int[] nums) {
int n = nums.length;
int[] result = new int[2];
int found = 0;
for (int i = 0; i < n && found < 2; i++) {
int count = 0;
for (int j = 0; j < n; j++) {
if (nums[j] == nums[i]) count++;
}
if (count == 1) result[found++] = nums[i];
}
Arrays.sort(result);
return result;
}
Complexity. Time O(n^2): up to n inner scans of length n. Space O(1) beyond the two-element output.
Approach 2: Better, frequency map
Intuition. Count every value once with a hash map instead of recounting. After one pass, the two keys with count 1 are the answer. This is simple and robust, and it is the approach that still works when the “everything else appears twice” guarantee is replaced by something odd; its cost is O(n) extra memory.
Algorithm.
- Build
freq, a map from value to count, in one pass. - Walk the entries and collect the two keys whose count is
1. - Sort the pair and return it.
static int[] better(int[] nums) {
Map<Integer, Integer> freq = new HashMap<>();
for (int x : nums) freq.merge(x, 1, Integer::sum);
int[] result = new int[2];
int found = 0;
for (Map.Entry<Integer, Integer> e : freq.entrySet()) {
if (e.getValue() == 1) result[found++] = e.getKey();
}
Arrays.sort(result);
return result;
}
Complexity. Time O(n): one pass to count plus one pass over at most n entries. Space O(n) for the map.
Approach 3: Optimal, XOR and split on a differing bit
Intuition. XOR of the whole array cancels every pair (x ^ x = 0), leaving xorAll = a ^ b, where a and b are the two singles. That alone does not reveal a or b, but it reveals something about them: every set bit of a ^ b is a position where a and b differ. Pick any such bit, say the lowest one. Now partition the array into two buckets: elements with that bit clear, and elements with that bit set. Two equal values land in the same bucket (they have identical bits), so every pair still cancels within its bucket. But a and b land in different buckets, because they differ at that bit. XOR each bucket and each one yields exactly one single.
The lowest set bit of v is v & -v. In two’s complement, -v flips every bit above the lowest set bit and keeps the lowest set bit and the zeros below it, so the AND isolates that single bit. Integer.lowestOneBit(v) does the same.
Algorithm.
- XOR all elements into
xorAll(equalsa ^ b). lowestBit = xorAll & -xorAll.- For each
x: if(x & lowestBit) == 0XOR it intobucket0, else intobucket1. - Return
{bucket0, bucket1}sorted.
static int[] optimal(int[] nums) {
int xorAll = 0;
for (int x : nums) xorAll ^= x; // pairs cancel, so this is a ^ b
int lowestBit = xorAll & -xorAll; // a and b differ at this bit, so it separates them
int bucket0 = 0, bucket1 = 0;
for (int x : nums) {
if ((x & lowestBit) == 0) bucket0 ^= x; else bucket1 ^= x;
}
int[] result = {bucket0, bucket1};
Arrays.sort(result);
return result;
}
Dry run on nums = [1, 2, 1, 3, 2, 5]. First pass: 1 ^ 2 ^ 1 ^ 3 ^ 2 ^ 5 = 3 ^ 5 = 011 ^ 101 = 110 = 6. Lowest set bit of 6 is 010 = 2, so we split on bit 1.
| x | binary | bit 1 | bucket0 after | bucket1 after |
|---|---|---|---|---|
| 1 | 001 | 0 | 001 | 000 |
| 2 | 010 | 1 | 001 | 010 |
| 1 | 001 | 0 | 000 | 010 |
| 3 | 011 | 1 | 000 | 001 |
| 2 | 010 | 1 | 000 | 011 |
| 5 | 101 | 0 | 101 | 011 |
bucket0 = 5, bucket1 = 3; sorted, the answer is [3, 5]. Both 1s went to bucket 0 and both 2s to bucket 1, and each pair cancelled where it landed.
Complexity. Time O(n): two linear passes. Space O(1): three integers.
Why does the choice of bit not matter? Any set bit of a ^ b separates a from b, and pairs always stay together regardless of which bit you pick. The lowest set bit is simply the cheapest to isolate.
Edge cases and pitfalls
xorAllis never zero. The two singles are distinct, soa ^ b != 0and a separating bit always exists. If the input violated that promise,lowestBitwould be0, everything would fall intobucket0, and the method would return{0, a ^ b}; the guarantee is what makes the approach valid.Integer.MIN_VALUE. IfxorAll == Integer.MIN_VALUE, then-xorAllis alsoInteger.MIN_VALUE(negation overflows), andxorAll & -xorAllis stillInteger.MIN_VALUE, which is the correct single sign bit. No special case is needed. The test[Integer.MIN_VALUE, Integer.MAX_VALUE, 3, 3]hasxorAll = -1and splits on bit 0.- Negative numbers. XOR and AND operate on raw bits;
[1, 1, -2, -8]yieldsxorAll = 6, splits on bit 1, and returns[-8, -2]. - One of the answers is zero.
[0, 1]returns[0, 1]. Zero in a bucket is just the result of that bucket’s XOR; nothing treats it as “missing”. - Separating bit is not bit 0. For
[8, 24, 100, 100],xorAll = 16and the split is on bit 4. Writingx & 1instead ofx & lowestBitis a common slip that only works by accident on small examples. - Minimum size input.
n = 2means both elements are the singles; the first pass gives their XOR and the split puts one in each bucket. - Output order. If the judge demands a specific order, sort; if any order is fine, the
Arrays.sorton two elements costs nothing measurable either way.
Related problems
- Single Number - I is the one-single version; this problem adds exactly one idea, the bucket split.
- Introduction to Bits and Tricks covers
x & -xand the other identities used here. - Set/Unset the rightmost unset bit is a companion trick for manipulating the lowest bits.
- Check if the i-th bit is Set or Not is the test used to assign each element to a bucket.