First Non-Repeating Character in a Stream medium

Description

Given a stream of characters, find the first non-repeating character at every point. If at some moment there is no non-repeating character, return # (or any sentinel) for that position.

Examples

> Case 1:
    Input:  s = "aabc"
    Output: "a#bb"
 
    After 'a':    'a' is the only char → first non-repeating = 'a'
    After 'aa':   'a' now repeats      → no non-repeating    = '#'
    After 'aab':  'a' repeats, 'b' once → first non-repeating = 'b'
    After 'aabc': 'a' repeats, 'b' once (came earlier), 'c' once → 'b'
 
> Case 2:
    Input:  s = "aabbcc"
    Output: "a##b##"

Constraints

  • Each query (per character) should be amortized O(1).
  • Stream can be arbitrarily long.

Intuition

We need two things, each updated quickly as new characters arrive:

  1. A frequency count of every character seen so far — so we know which ones repeat.
  2. An ordered list of “candidates” — characters that might still be the first non-repeating one. As soon as a character’s count goes above 1, it’s no longer a candidate.

A queue is perfect for the candidate list: characters join at the back in the order they arrive, and we always check the front.

After each new character:

  1. Enqueue it.
  2. Increment its frequency.
  3. Clean up the front: while the front of the queue has frequency > 1, dequeue it.
  4. The front of the queue (or # if empty) is the current answer.
stream = "aabc"

'a': freq={a:1}        queue=[a]           front 'a' freq 1 → answer 'a'
'a': freq={a:2}        queue=[a,a]         front 'a' freq 2 → dequeue. front 'a' again, freq 2 → dequeue.
                                            queue=[]        → answer '#'
'b': freq={a:2,b:1}    queue=[b]           front 'b' freq 1 → answer 'b'
'c': freq={a:2,b:1,c:1} queue=[b,c]         front 'b' freq 1 → answer 'b'

Code

Why this is amortized O(1)

Each character is enqueued once and dequeued at most once across the entire stream. Total queue work across n chars is O(n) — that’s O(1) per character on average.

Two things to notice. First: the frequency array is just for checking — the queue is what gives us order. Second: characters never re-enter the queue once dequeued, even if we encounter them again. They’re permanently “repeating” from then on.

Common variation

A close cousin: “first unique character in a string” (one-shot, not streaming). For that, two passes over the string with a frequency map is simpler than a queue. The queue earns its keep when you have to answer the question after every character in a stream.

Analysis

  • Time: O(n) total, O(1) amortized per character.
  • Space: O(σ) where σ is the alphabet size (26 here). The queue is also bounded by n in the worst case, but in practice it shrinks aggressively.