Before attempting this problem, you should be comfortable with:
The straightforward approach stores all numbers in a queue. When asked for the first unique number, we scan through the queue and count occurrences of each element. The first element with count 1 is our answer.
1, or -1 if none exists.Time complexity:
constructor:
add():
showFirstUnique():
Space complexity:
Where is the length of the initial array passed into the constructor and is the total number of items added into the queue so far (including those from the constructor).
We can speed up uniqueness checks by maintaining a hash map that tracks whether each number is unique. The queue preserves insertion order. When showing the first unique, we pop non-unique elements from the front of the queue until we find a unique one or the queue is empty.
add().-1.class FirstUnique:
def __init__(self, nums: List[int]):
self._queue = deque(nums)
self._is_unique = {}
for num in nums:
# Notice that we're calling the "add" method of FirstUnique; not of the queue.
self.add(num)
def showFirstUnique(self) -> int:
# We need to start by "cleaning" the queue of any non-uniques at the start.
# Note that we know that if a value is in the queue, then it is also in
# is_unique, as the implementation of add() guarantees this.
while self._queue and not self._is_unique[self._queue[0]]:
self._queue.popleft()
# Check if there is still a value left in the queue. There might be no uniques.
if self._queue:
return self._queue[0] # We don't want to actually *remove* the value.
return -1
def add(self, value: int) -> None:
# Case 1: We need to add the number to the queue and mark it as unique.
if value not in self._is_unique:
self._is_unique[value] = True
self._queue.append(value)
# Case 2 and 3: We need to mark the number as no longer unique.
else:
self._is_unique[value] = FalseTime complexity:
constructor:
add():
showFirstUnique(): (amortized)
Space complexity:
Where is the length of the initial array passed into the constructor and is the total number of items added into the queue so far (including those from the constructor).
Instead of lazily removing non-unique elements during showFirstUnique(), we can eagerly remove them when they become non-unique. Using a LinkedHashSet (or OrderedDict) allows O(1) removal by value while preserving insertion order. This makes showFirstUnique() a true O(1) operation.
add().-1.# In Python, we have to make do with the OrderedDict class. We can use it as a Set by setting
# the values to None.
class FirstUnique:
def __init__(self, nums: List[int]):
self._queue = OrderedDict()
self._is_unique = {}
for num in nums:
# Notice that we're calling the "add" method of FirstUnique; not of the queue.
self.add(num)
def showFirstUnique(self) -> int:
# Check if there is still a value left in the queue. There might be no uniques.
if self._queue:
# We don't want to actually *remove* the value.
# Seeing as OrderedDict has no "get first" method, the way that we can get
# the first value is to create an iterator, and then get the "next" value
# from that. Note that this is O(1).
return next(iter(self._queue))
return -1
def add(self, value: int) -> None:
# Case 1: We need to add the number to the queue and mark it as unique.
if value not in self._is_unique:
self._is_unique[value] = True
self._queue[value] = None
# Case 2: We need to mark the value as no longer unique and then
# remove it from the queue.
elif self._is_unique[value]:
self._is_unique[value] = False
self._queue.pop(value)
# Case 3: We don't need to do anything; the number was removed from the queue
# the second time it occurred.Time complexity:
constructor:
add():
showFirstUnique():
Space complexity:
Where is the length of the initial array passed into the constructor and is the total number of items added into the queue so far (including those from the constructor).
In approaches that use both a queue and a hash map, calling add() for initial numbers while also directly pushing them to the queue results in duplicates. Either iterate through the initial array and call add() for each element (which handles both structures), or manually handle both structures without calling add(). Mixing both approaches corrupts the data structure.
When lazily removing non-unique elements, the queue may contain stale entries that are no longer unique. Failing to pop these entries before returning the front element causes showFirstUnique() to return a non-unique number. Always check the hash map status and remove invalidated entries from the front before returning.
When a number is added for the third or subsequent time, its uniqueness status should remain false. A common bug is toggling the status back to true or re-adding it to the queue. The hash map should transition from "not seen" to "unique" to "non-unique" and never go back.