Problem Overview
LeetCode 137 Single Number II presents an integer array where every element appears exactly three times except for exactly one element that appears only once. Your task is to find that unique element. The brute-force approach of counting occurrences with a hash map solves this in O(n) time but uses O(n) space — and the problem explicitly demands O(1) extra space.
The O(1) space constraint rules out hash maps, sorting, and any auxiliary data structure that scales with input size. You need a mathematical or bitwise technique that processes numbers in place and accumulates state using only a fixed number of variables regardless of input size.
The challenge compared to Single Number I (LeetCode 136) is that pairs cancel neatly with XOR — every bit that appears twice becomes 0 — but triplets do not. A bit that appears three times passes through the XOR filter unchanged, so naive XOR returns the unique element only when duplicates come in pairs.
- Given: integer array nums where every element appears exactly 3 times except one unique element
- Goal: return the unique element that appears exactly once
- Constraint: O(n) time and O(1) extra space — no hash maps or sorting allowed
- Key difficulty: triplets do not cancel with XOR the way pairs do (a^a^a = a, not 0)
- Must find a bitwise technique that tracks count mod 3 for each bit position
Why XOR Alone Fails
XOR is the perfect tool for pairs because any bit XORed with itself twice becomes 0: a ^ a = 0. This means XOR-ing all elements in Single Number I leaves only the unpaired bit pattern — the unique element. The key property is that XOR counts bits mod 2, and pairs reduce to 0 mod 2.
For triplets the mod changes to 3. A bit that appears three times satisfies count mod 3 = 0 — it should vanish. But XOR gives 1 ^ 1 ^ 1 = 1 (XOR counts mod 2, not mod 3), so a bit appearing three times does NOT cancel out. The three XOR operations return a 1 where we needed a 0.
To cancel bits that appear three times, we need arithmetic mod 3, not mod 2. Each bit position must independently track whether it has been seen 0, 1, or 2 times mod 3. When the count reaches 3 (i.e., 0 mod 3), that bit resets to zero. Two bits of state per position are needed — which is exactly what the ones and twos variables provide.
XOR vs Mod-3 Counter
Single Number I uses XOR because pairs cancel: a^a = 0, so XOR counts bits mod 2. For triplets, you need a mod-3 counter for each bit position. The state machine with ones and twos encodes a 2-bit counter (states 0, 1, 2) per bit using only two integer variables, achieving O(1) space across all 32 bit positions simultaneously.
Bit Counting Approach
Before reaching the optimal state machine, it helps to understand the bit-counting approach. For each of the 32 bit positions (or 64 for 64-bit integers), count how many numbers in the array have a 1 at that position. Take that count modulo 3. If the result is 1, the unique number has a 1 at that bit position; if 0, it has a 0.
This works because every repeated element contributes its bit either 0 or 3 times to any given position. Three contributions mod 3 equals 0, so repeated elements vanish. The only remaining 1s in the mod-3 counts come from the unique element, perfectly reconstructing it bit by bit.
The bit counting approach runs in O(32 * n) = O(n) time and O(1) space, satisfying both constraints. It is straightforward to implement and easy to verify. The state machine approach we cover next achieves the same complexity but processes all 32 bit positions simultaneously in a single pass, making it more elegant and slightly faster in practice.
- 1For each bit position b from 0 to 31, initialize count = 0
- 2Iterate through all nums: if (num >> b) & 1 == 1, increment count
- 3After the loop, set result bit b to count % 3
- 4After processing all 32 positions, return the reconstructed result integer
- 5Time: O(32n) = O(n); Space: O(1) — only count and result variables used
State Machine with ones/twos
The state machine approach uses two integer variables — ones and twos — as a pair of 32-bit arrays of 2-bit counters. The variable ones holds bits that have appeared 1 mod 3 times across all positions simultaneously. The variable twos holds bits that have appeared 2 mod 3 times. When a bit appears a third time, it clears from both ones and twos, returning to state 00.
The update rules are: ones = (ones ^ num) & ~twos and twos = (twos ^ num) & ~ones. The XOR with num flips the bits where num has a 1, advancing the counter state. The AND NOT mask clears bits that have just reached the third state in the other variable, enforcing the mod-3 rollover. These two lines together implement all four state transitions in a single pass.
After processing every number in the input, ones contains exactly the bits that appeared 1 mod 3 times across all elements. Since every element except the unique one appears exactly 3 times (0 mod 3), ones holds only the bits of the unique element. The answer is simply the value of ones at the end.
State Machine Transitions
The 2-bit counter for each bit position cycles through states: 00 → 01 → 10 → 00 (binary: none → ones → twos → clear). When a bit appears once it enters ones (01). When it appears again it moves to twos (10). On the third appearance both clear to 00. The AND-NOT masks enforce this circular counter: ~twos prevents ones from setting a bit already in twos, and ~ones prevents twos from setting a bit already cleared back to ones.
Walk-Through Example
Consider nums = [2, 2, 3, 2]. In binary: 2 = 010, 3 = 011. The number 2 appears three times and 3 appears once, so the answer should be 3. Let us trace the state machine step by step starting with ones = 0 and twos = 0.
After num = 2 (010): ones = (0 ^ 2) & ~0 = 2 & 0xFFFF = 010; twos = (0 ^ 2) & ~2 = 2 & ~2 = 0. State: ones=010, twos=000. After num = 2 again: ones = (010 ^ 010) & ~000 = 000; twos = (000 ^ 010) & ~000 = 010. State: ones=000, twos=010.
After num = 3 (011): ones = (000 ^ 011) & ~010 = 011 & 101 = 001; twos = (010 ^ 011) & ~001 = 001 & 110 = 000. State: ones=001, twos=000. After num = 2 (010): ones = (001 ^ 010) & ~000 = 011; twos = (000 ^ 010) & ~011 = 010 & 100 = 000. State: ones=011, twos=000. Result: ones = 011 = 3. Correct!
- 1Start: ones=0, twos=0
- 2num=2 (010): ones=(0^2)&~0=2; twos=(0^2)&~2=0 → ones=010, twos=000
- 3num=2 (010): ones=(2^2)&~0=0; twos=(0^2)&~0=2 → ones=000, twos=010
- 4num=3 (011): ones=(0^3)&~2=3&~2=001; twos=(2^3)&~1=1&~1=000 → ones=001, twos=000
- 5num=2 (010): ones=(1^2)&~0=3; twos=(0^2)&~3=2&~3=000 → ones=011=3 ✓
Code Walkthrough — Python and Java
The state machine solution in Python is remarkably compact. Initialize ones = 0 and twos = 0, then loop through each num in nums applying the two update lines: ones = (ones ^ num) & ~twos followed immediately by twos = (twos ^ num) & ~ones. Note the order matters — ones must be updated first. Return ones. Time: O(n), Space: O(1).
Java handles the same logic identically since Java integers are fixed 32-bit two's complement. Use int ones = 0, twos = 0; then in the loop: ones = (ones ^ num) & ~twos; twos = (twos ^ num) & ~ones;. Return ones. The bitwise operators behave the same in both languages for positive numbers; the difference appears only with negative unique elements in Python.
The bit-counting version is also O(n) O(1) but uses a nested loop over 32 bit positions. For each bit b, sum up (num >> b) & 1 for all nums, then set result bit b to that sum % 3. Reconstruct the result integer from the 32 bits. Both approaches solve the problem; the state machine is the interview-expected elegant solution.
Python Negative Number Handling
Python handles negative numbers differently from Java and C++ because Python integers have arbitrary precision — there is no fixed 32-bit boundary. If the unique number can be negative (as it can in LeetCode 137), apply 32-bit masking: mask the result with 0xFFFFFFFF to get unsigned 32 bits, then use ctypes.c_int32(result).value or the sign-extension trick (result - 0x100000000 if result >= 0x80000000 else result) to convert back to a signed integer. The state machine itself works without modification in Java and C++.