First Non-Repeating Character in a Stream
The queue keeps candidates in arrival order, so the answer is always the front
Re-scanning everything seen so far after each character is the obvious answer and it is quadratic. The insight is that 'first' means oldest, and a queue already stores things oldest-first — so the answer is never searched for, it is just the front. A character joins the back when it arrives; once its count passes one it is dead, but deleting it from the middle would cost a scan, so it is left in place and simply thrown away if it ever reaches the front. Every character is therefore pushed once and popped at most once, and each answer is a single peek.
Details
Animated on: stream = "aabcbd" — after every character, report the first character so far that has appeared exactly once, or '#' if there is none. The row drawn is the QUEUE of surviving candidates, not the stream.
Problem:Stream First Non-repeatingMedium
The queue keeps candidates in arrival order, so the answer is always the front
step 1 / 13
'a' arrives and is brand new, so it joins the BACK of the queue as the newest candidate. The queue is now [a] — every character still in the running, in the order it showed up, which is exactly the order the answer has to be picked in.
- front=0· arr[0] = a
- back=0· arr[0] = a
1FUNCTION firstNonRepeating(stream)2 queue <- empty queue3 count <- empty map4 FOR EACH ch IN stream5 count[ch] <- count[ch] + 16 ENQUEUE ch INTO queue7 WHILE queue IS NOT EMPTY AND count[FRONT(queue)] > 18 DEQUEUE FROM queue9 IF queue IS EMPTY10 OUTPUT '#'11 ELSE12 OUTPUT FRONT(queue)
← / → step · space play · Home restart
Every step in words
Show all 13 steps
- 'a' arrives and is brand new, so it joins the BACK of the queue as the newest candidate. The queue is now [a] — every character still in the running, in the order it showed up, which is exactly the order the answer has to be picked in.
- The front 'a' has been seen exactly once, so the drop loop does not run at all — no scan, no search. Queue: [a]. The front 'a' is the oldest character still seen exactly once, so after "a" the answer is 'a'. Output so far: "a".
- 'a' arrives for the second time, so it can never be the answer again — and neither can the copy already sitting at index 0. Both are marked dead, but NOT removed: hunting through the queue to delete them would cost a scan every character. They are dropped only when they reach the front.
- The front is stale, so drop it: 'a' (seen 2 times), then 'a' (seen 2 times). That emptied the queue: every character in "aa" has repeated, so there is no non-repeating one and the answer is '#'. Output so far: "a#".
- 'b' arrives and is brand new, so it joins the BACK of the queue as the newest candidate. The queue is now [b] — every character still in the running, in the order it showed up, which is exactly the order the answer has to be picked in.
- The front 'b' has been seen exactly once, so the drop loop does not run at all — no scan, no search. Queue: [b]. The front 'b' is the oldest character still seen exactly once, so after "aab" the answer is 'b'. Output so far: "a#b".
- 'c' arrives and is brand new, so it joins the BACK of the queue as the newest candidate. The queue is now [b, c] — every character still in the running, in the order it showed up, which is exactly the order the answer has to be picked in.
- The front 'b' has been seen exactly once, so the drop loop does not run at all — no scan, no search. Queue: [b, c]. The front 'b' is the oldest character still seen exactly once, so after "aabc" the answer is 'b'. Output so far: "a#bb".
- 'b' arrives for the second time, so it can never be the answer again — and neither can the copy already sitting at index 0. Both are marked dead, but NOT removed: hunting through the queue to delete them would cost a scan every character. They are dropped only when they reach the front.
- The front is stale, so drop it: 'b' (seen 2 times). Queue: [c, b]. The front 'c' is the oldest character still seen exactly once, so after "aabcb" the answer is 'c'. Output so far: "a#bbc".
- 'd' arrives and is brand new, so it joins the BACK of the queue as the newest candidate. The queue is now [c, b, d] — every character still in the running, in the order it showed up, which is exactly the order the answer has to be picked in.
- The front 'c' has been seen exactly once, so the drop loop does not run at all — no scan, no search. Queue: [c, b, d]. The front 'c' is the oldest character still seen exactly once, so after "aabcbd" the answer is 'c'. Output so far: "a#bbcc".
- Stream "aabcbd" fully consumed, answers "a#bbcc". Each character was pushed once and popped at most once, so all 6 answers cost O(6) work in total — the queue never had to be searched, only its front read.
Now solve it yourself
Watching is the first half. The second is writing it without the animation.