Coding Interview Patterns Module 3 – Arrays and hashing
Knowing what to track in one pass
After every character of a stream, report the first one seen exactly once so far, and learn to choose the state a one-pass loop must keep.
What you will learn
- Decide which summary state (counts, first index, last index, running best) answers a query in one pass
- Report the first value seen exactly once after every arrival in O(n) total time
- Check a bijection with two maps, and show the input that fools a single map
- Use first- and last-occurrence tables to answer questions about distances and spans
Before you start
What you’ll learn · Before you start
What you will learn
- Decide which summary state (counts, first index, last index, running best) answers a query in one pass
- Report the first value seen exactly once after every arrival in O(n) total time
- Check a bijection with two maps, and show the input that fools a single map
- Use first- and last-occurrence tables to answer questions about distances and spans
Before you start
On this page
After every message of the stream zxzyx, a quiz screen must show the earliest letter sent exactly once so far:
z z x x y. Knowing what to track means deciding, before you write the loop, what a single pass must remember about
the items it has already read, so that it can answer for the current item at once. The answer is usually a small
table: a count for each value, the first or last position of each value, or one running best, and choosing it is most
of the algorithm. With the right state the screen costs a few steps per character; without it, a scan of everything
read so far.
The problem: the letter sent only once
The problem
A radio quiz takes answers by text message, one lowercase letter per message. After every message, the studio
screen shows a letter that has arrived in exactly one message so far, the earliest such letter if there are
several, or # when every letter so far has arrived at least twice. Given the messages in the order they arrive,
return what the screen shows after each one.
Constraints: up to 2 × 10⁵ messages, each a letter from a to z; the screen must change before the next message.
| Messages so far | Sent once | Screen |
|---|---|---|
z |
z | z |
zx |
z, x | z |
zxz |
x | x |
zxzy |
x, y | x |
zxzyx |
y | y |
So zxzyx gives z z x x y, aabb gives a # b #, and q gives q.
The brute force: counts and a scan from the start
Keep a count for each letter and, after every message, scan the messages from the first one until a letter with a count of 1 turns up. The counts cost O(1) per message, but the scan may read every message so far:
"""The screen after every message, by brute force: keep counts, then scan the messages from the start each time."""
import random
def screen_brute(messages):
"""For each prefix, the earliest letter sent exactly once so far, or '#'. Also returns the letters scanned."""
counts, shown, scanned = {}, [], 0
for k, letter in enumerate(messages):
counts[letter] = counts.get(letter, 0) + 1
answer = "#"
for earlier in messages[: k + 1]: # every message so far, oldest first
scanned += 1
if counts[earlier] == 1:
answer = earlier
break
shown.append(answer)
return " ".join(shown), scanned
for messages in ("zxzyx", "aabb", "q"):
print(f"{messages} -> {screen_brute(messages)[0]}")
rng = random.Random(26)
for n in (1_000, 2_000, 4_000):
messages = "".join(rng.choice("abcdefghijklmnopqrstuvwxyz") for _ in range(n))
print(f"n = {n:,}: {screen_brute(messages)[1]:,} letters scanned") Output
zxzyx -> z z x x y aabb -> a # b # q -> q n = 1,000: 492,532 letters scanned n = 2,000: 1,993,561 letters scanned n = 4,000: 7,995,121 letters scanned
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brute.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The scans add up to about n²/2 letters: 7,995,121 for 4,000 random messages, four times as many for each doubling.
For 200,000 messages that is about 2 × 10¹⁰ letters, far more than any time limit allows, and recounting every prefix
from scratch would be worse still, O(n³). Step through the rescans on zxzyx:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- After z. z has been sent once. The scan reads message 1, z, whose count is 1, so the screen shows z after 1 letter read.
- After x. The scan starts again at message 1. z is still sent once, so the screen shows z. 2 letters read so far.
- After the second z. z has now been sent twice. The scan passes it and finds x at message 2. 4 letters read so far.
- After y. The scan passes z again and stops at x, still sent once. 6 letters read so far.
- After the second x. z, x and z are passed, and y at message 4 is the earliest letter sent once. 10 letters read in all.
- Why it is slow. Every message starts a new scan from the first one, so the scans add up to about n²/2 letters, 7,995,121 for 4,000 random messages in the recorded run.
What to notice
The answer depends on only two facts about the past, how often each letter has been sent and the order in which letters were first sent. A count never goes down, so a letter sent twice can never be the answer again, and the answer only moves forward along the letters in order of first arrival, as the screen column of the table shows: z, then x, then y. A queue of candidates keeps letters in that order and drops a letter from its front once it has been sent twice. An insertion order: The order in which keys were first added to a map. Python's dict, JavaScript's Map and Java's LinkedHashMap iterate in insertion order; a plain Java HashMap promises no order at all. map, such as Python’s dict or Java’s LinkedHashMap, can hold just the letters sent once. With only 26 letters, scanning 26 counters is a third way.
Checkpoint
Check: The earliest letter sent exactly once · Counts and a scan from the start, and its cost · What the past must tell the loop
The practice below starts with whatever you know least.
Practice that adapts to you
Practise this lesson
9 questions about 10 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Counts and a queue of candidates, step by step Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- A dry run on zxzyx
- Why it is correct and O(n), with the work measured
- The same screen in 14 languages
- An ordered map instead of a queue, and where it slows down
- Choosing the state for other one-pass questions
- Two maps for a bijection, and the input that fools one
- Edge cases, tests and variants
- Edge cases and the tests that catch them
- Variants
- Practice: the first ticket read once
- The first value seen once, after every arrival
- Interview questions
- Before you write a one-pass loop over an array or a stream, how do you decide what state to keep?
- A gate scanner streams ticket IDs, up to 10⁷ arrivals and 10⁶ distinct IDs, and a dashboard must show after each arrival the earliest ID read exactly once so far. What state do you keep, how much memory does it need, and which structures in your language keep the work per arrival constant?
- A function checks whether a pattern such as abba matches a sentence such as red blue blue red, one word per letter. It keeps one dictionary from letters to words and passes every test the team wrote. Which input breaks it, how do you fix it, and how would you have found it without guessing?
- The radio screen now counts only the last k messages: after every message it shows the earliest letter sent exactly once within the last k messages, or #. Why does the queue of candidates stop working, and what do you use instead, for letters and for arbitrary IDs?
- Key takeaways
1 diagram · 7 runnable examples (JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 40 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Terms in this lesson
- bijection
- A pairing in which every item on one side goes with exactly one item on the other side, and the other way round, such as the letters of a pattern and the words of a sentence that matches it.
- insertion order
- The order in which keys were first added to a map. Python's dict, JavaScript's Map and Java's LinkedHashMap iterate in insertion order; a plain Java HashMap promises no order at all.
References
- collections: deque and OrderedDict (Python Software Foundation)
- Built-in Types: mapping types (dict keeps insertion order) (Python Software Foundation)
- sys.settrace: set the trace function (Python Software Foundation)
- Java SE 25 API: LinkedHashMap (Oracle)
- Java SE 25 API: ArrayDeque (Oracle)
- ECMAScript language specification: Map objects (Ecma International (TC39))
- Working Draft, Programming Languages: C++ (N4950): [deque], [array] (ISO/IEC JTC1/SC22/WG21)
- The Go Programming Language Specification: slice expressions (The Go Authors)
- Java SE 25 API: ArrayDeque (Oracle)
- Rust standard library: VecDeque (The Rust Project)
- Queue<T> class (.NET API) (Microsoft)
- PHP manual: the SplQueue class (The PHP Group)
- ArrayDeque (Kotlin standard library) (JetBrains (Kotlin))
- ListQueue class (dart:collection) (Dart (Google))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress