LeetCode 137 - Single Number II
This problem asks us to find a unique number in an array where every other number appears exactly three times. In simpler terms, if you imagine counting all the numbers in the array, every number except one will show up three times, and our task is to identify the number that…
Difficulty: 🟡 Medium
Topics: Array, Bit Manipulation
Solution
Problem Understanding
This problem asks us to find a unique number in an array where every other number appears exactly three times. In simpler terms, if you imagine counting all the numbers in the array, every number except one will show up three times, and our task is to identify the number that only appears once.
The input is a list of integers nums, with a size constraint of up to 3 * 10^4, which is relatively large. The numbers themselves can be negative, zero, or positive, up to the 32-bit signed integer range. The output is a single integer, the number that does not repeat three times.
Edge cases to keep in mind include arrays with the smallest size of 1 (where the only number is the answer), arrays with negative numbers, and arrays where the unique number is zero. The problem guarantees that there is exactly one unique number, so we do not need to handle multiple single numbers or empty arrays.
The key challenge is that the solution must run in linear time and use constant extra space, which rules out typical approaches using hash maps or sorting.
Approaches
The brute-force approach is to use a hash map or dictionary to count the occurrences of each number. Once all counts are known, we can iterate through the dictionary to find the number that occurs once. This approach is simple and correct, but it uses O(n) extra space, which violates the problem's constant space constraint.
The optimal approach leverages bit manipulation. Since numbers are integers with a fixed number of bits (32 bits in this case), we can examine each bit position across all numbers. For each bit position, if we sum all the bits in that position across the array and take modulo 3, the sum will be zero for bits belonging to numbers appearing three times. The remaining sum will correspond to the bits of the unique number. Using this insight, we can reconstruct the single number without extra memory beyond a few integer variables.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n) | O(n) | Use a hash map to count occurrences, simple but violates constant space constraint |
| Optimal | O(n) | O(1) | Use bitwise operations to track bits counts modulo 3, reconstruct the single number efficiently |
Algorithm Walkthrough
- Initialize two integer variables,
onesandtwos, to zero. These variables will track bits that have appeared once and twice respectively. - Iterate over each number
numin the array. - Update
twoswith bits that have appeared twice. This is done usingtwos |= ones & num. - Update
oneswith bits that have appeared once. Useones ^= num. - Compute a mask for bits that have appeared three times:
common_mask = ~(ones & twos). - Clear the bits in
onesandtwosthat have appeared three times:ones &= common_maskandtwos &= common_mask. - After processing all numbers,
oneswill contain the bits of the unique number. Returnones.
Why it works:
The algorithm maintains a bitwise count of appearances modulo 3 for each bit position. Whenever a bit has appeared three times, it is cleared from both ones and twos, effectively resetting the count for that bit. At the end, only the bits corresponding to the unique number remain in ones.
Python Solution
from typing import List
class Solution:
def singleNumber(self, nums: List[int]) -> int:
ones, twos = 0, 0
for num in nums:
twos |= ones & num
ones ^= num
common_mask = ~(ones & twos)
ones &= common_mask
twos &= common_mask
return ones
In this implementation, ones keeps track of bits seen once, twos keeps track of bits seen twice, and common_mask clears bits that have appeared three times. By the end, ones contains the unique number.
Go Solution
func singleNumber(nums []int) int {
ones, twos := 0, 0
for _, num := range nums {
twos |= ones & num
ones ^= num
commonMask := ^(ones & twos)
ones &= commonMask
twos &= commonMask
}
return ones
}
The Go implementation mirrors the Python solution. We use integers ones and twos to track bit counts, and commonMask to reset bits that have appeared three times. Go's bitwise operations work identically to Python for 32-bit integers.
Worked Examples
Example 1: nums = [2,2,3,2]
| Iteration | num | ones | twos | common_mask |
|---|---|---|---|---|
| 1 | 2 | 2 | 0 | -1 |
| 2 | 2 | 0 | 2 | -3 |
| 3 | 3 | 1 | 2 | -2 |
| 4 | 2 | 3 | 0 | -1 |
Final ones = 3 → output 3.
Example 2: nums = [0,1,0,1,0,1,99]
| Iteration | num | ones | twos | common_mask |
|---|---|---|---|---|
| 1 | 0 | 0 | 0 | -1 |
| 2 | 1 | 1 | 0 | -1 |
| 3 | 0 | 1 | 0 | -1 |
| 4 | 1 | 0 | 1 | -2 |
| 5 | 0 | 0 | 0 | -1 |
| 6 | 1 | 0 | 0 | -1 |
| 7 | 99 | 99 | 0 | -1 |
Final ones = 99 → output 99.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each number is processed once, and all bitwise operations are constant time. |
| Space | O(1) | Only a fixed number of integer variables are used, independent of input size. |
The solution meets the requirement of linear runtime with constant extra space.
Test Cases
# provided examples
assert Solution().singleNumber([2,2,3,2]) == 3 # basic test
assert Solution().singleNumber([0,1,0,1,0,1,99]) == 99 # unique at the end
# edge cases
assert Solution().singleNumber([7]) == 7 # only one element
assert Solution().singleNumber([0,0,0,-1]) == -1 # negative unique
assert Solution().singleNumber([1,1,1,0]) == 0 # unique is zero
assert Solution().singleNumber([-2,-2,-2,-5]) == -5 # negative numbers
assert Solution().singleNumber([10**6,10**6,10**6,123456]) == 123456 # large numbers
| Test | Why |
|---|---|
| [2,2,3,2] | Basic functionality with small positive numbers |
| [0,1,0,1,0,1,99] | Unique number at the end, mixed values |
| [7] | Single-element array |
| [0,0,0,-1] | Negative unique number |
| [1,1,1,0] | Unique is zero |
| [-2,-2,-2,-5] | All numbers negative |
| [106,106,10**6,123456] | Large numbers to check bit handling |
Edge Cases
Single-element array: If the array has only one element, it is trivially the unique number. Our implementation handles this naturally because the loop runs once, and ones ends up as that element.
Negative numbers: Bit manipulation works correctly with negative numbers in Python and Go because the operations apply to the two's complement binary representation. This ensures ones reconstructs negative numbers correctly.
Unique number is zero: Zero has no set bits, so it might seem tricky, but the algorithm handles it correctly because ones starts at zero and will remain zero if all numbers appear three times except zero. This ensures zero is returned accurately.