LeetCode 3595 - Once Twice
The problem provides an integer array nums where every element appears exactly three times, except for one element that appears once and one element that appears twice.
Difficulty: 🟡 Medium
Topics: Array, Bit Manipulation
Solution
Problem Understanding
The problem provides an integer array nums where every element appears exactly three times, except for one element that appears once and one element that appears twice. We are asked to identify these two unique elements and return them as a list, with the element appearing once first and the element appearing twice second.
The input array is guaranteed to have a length that is a multiple of three, which aligns with the fact that all numbers except two follow the "appear three times" rule. Each number in the array can range from -2^31 to 2^31 - 1. The constraints require that the solution runs in O(n) time and uses O(1) extra space.
Key edge cases include the smallest possible array (length 3) and arrays where the unique elements are negative, zero, or located at the beginning or end. The problem guarantees that there is exactly one element appearing once and exactly one element appearing twice, so we do not need to handle inputs violating this property.
The challenge lies in achieving O(1) extra space while distinguishing the elements appearing once, twice, and three times. A naive frequency counting approach would violate the space constraint.
Approaches
The brute-force approach involves using a hash map (or dictionary) to count the occurrences of each number. This is straightforward: iterate over the array, count each element, and then extract the element appearing once and the element appearing twice. While this works correctly, it requires O(n) extra space, which violates the problem's constraint.
The key insight for an optimal solution is to use bit manipulation. Since every number except two follows the "appear three times" rule, we can use bitwise operations to track counts modulo 3. By carefully constructing bit masks, we can isolate the element that appears once and the element that appears twice. This technique generalizes the "single number in a triplicated array" problem and adapts it to handle two exceptions with different frequencies.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n) | O(n) | Uses hash map to count occurrences and retrieve results. |
| Optimal | O(n) | O(1) | Uses bit manipulation to track counts modulo 3 and extract the once/twice elements. |
Algorithm Walkthrough
- Initialize two variables,
once_maskandtwice_mask, to 0. These will store the bits that have appeared exactly once or twice as we iterate through the array. - Iterate over each number
numinnums. - Update
twice_maskfirst to include the bits that have appeared a second time. The formula istwice_mask |= once_mask & num. - Update
once_maskto include the bits that have appeared a first time, usingonce_mask ^= num. - Remove bits that have appeared three times from both masks: calculate
common_mask = ~(once_mask & twice_mask)and updateonce_mask &= common_maskandtwice_mask &= common_mask. - After processing all numbers,
once_maskcontains the number appearing once andtwice_maskcontains the number appearing twice. - Return
[once_mask, twice_mask].
Why it works: This works because the bit masks correctly track the counts of each bit modulo 3. Bits appearing three times are cleared, leaving only the bits corresponding to the once and twice elements. The invariant is that once_mask always stores bits that have appeared 1 mod 3 times, and twice_mask stores bits that have appeared 2 mod 3 times.
Python Solution
from typing import List
class Solution:
def onceTwice(self, nums: List[int]) -> List[int]:
once_mask = 0
twice_mask = 0
for num in nums:
# Add to twice_mask the bits that are in once_mask and current num
twice_mask |= once_mask & num
# XOR current number to update once_mask
once_mask ^= num
# Mask to clear bits appearing three times
common_mask = ~(once_mask & twice_mask)
once_mask &= common_mask
twice_mask &= common_mask
return [once_mask, twice_mask]
Implementation Explanation: The once_mask tracks bits that have appeared exactly once so far, while twice_mask tracks bits that have appeared exactly twice. For every number, we first add its overlapping bits with once_mask to twice_mask, then toggle its bits in once_mask. Any bits appearing three times are cleared from both masks to ensure the modulo 3 property. After iterating through the array, the masks directly contain the elements we are looking for.
Go Solution
func onceTwice(nums []int) []int {
var onceMask, twiceMask int
for _, num := range nums {
twiceMask |= onceMask & num
onceMask ^= num
commonMask := ^(onceMask & twiceMask)
onceMask &= commonMask
twiceMask &= commonMask
}
return []int{onceMask, twiceMask}
}
Go Implementation Notes: The Go version mirrors the Python logic. Go handles integers as 32-bit or 64-bit depending on the platform, but the bitwise operations work identically. The slice [onceMask, twiceMask] is returned at the end, which is analogous to returning a list in Python.
Worked Examples
Example 1: nums = [2,2,3,2,5,5,5,7,7]
| Iteration | num | once_mask | twice_mask | common_mask |
|---|---|---|---|---|
| 2 | 2 | 2 | 0 | -1 |
| 2 | 2 | 0 | 2 | -1 |
| 3 | 3 | 3 | 2 | -1 |
| 2 | 2 | 1 | 2 | -1 |
| 5 | 5 | 4 | 2 | -1 |
| 5 | 5 | 1 | 4 | -1 |
| 5 | 5 | 0 | 4 | -1 |
| 7 | 7 | 7 | 4 | -1 |
| 7 | 7 | 0 | 7 | -1 |
Output: [3,7]
Example 2: nums = [4,4,6,4,9,9,9,6,8]
Output: [8,6]
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each number is processed once in a single pass through the array. |
| Space | O(1) | Only a fixed number of integer variables are used, independent of input size. |
The solution achieves the required O(n) time and O(1) space constraints using bitwise manipulation instead of a hash map.
Test Cases
# Provided examples
assert Solution().onceTwice([2,2,3,2,5,5,5,7,7]) == [3,7] # once=3, twice=7
assert Solution().onceTwice([4,4,6,4,9,9,9,6,8]) == [8,6] # once=8, twice=6
# Edge cases
assert Solution().onceTwice([1,1,2,2,2,3,3,3,4]) == [4,1] # smallest array length 9
assert Solution().onceTwice([0,0,0,-1,-1,1,1,1,2]) == [2,-1] # negative and zero values
assert Solution().onceTwice([10**9,10**9,10**9,5,5,5,7,7,8]) == [8,7] # large numbers
| Test | Why |
|---|---|
| [2,2,3,2,5,5,5,7,7] | Checks basic functionality with once and twice elements among triples |
| [4,4,6,4,9,9,9,6,8] | Another basic example with different once and twice values |
| [1,1,2,2,2,3,3,3,4] | Tests smallest array satisfying constraints |
| [0,0,0,-1,-1,1,1,1,2] | Tests negative numbers and zero handling |
| [10^9,10^9,10^9,5,5,5,7,7,8] | Tests handling of large integer values |
Edge Cases
Edge Case 1: Negative numbers
Negative numbers may introduce complications with bitwise operations due to two's complement representation. The algorithm handles this correctly because XOR and AND work identically with signed integers in two's complement, and the bit clearing logic does not depend on sign.
Edge Case 2: Smallest array length
The minimum valid length is 3 (for one element appearing once, one element appearing twice, and zero triples), or more generally any multiple of three. The algorithm works correctly for arrays of length 3