</> MAANG.io
coding interview · 201

Intermediate

Advanced coding interview preparation covering complex algorithms, system design, and optimization techniques for senior-level positions.

0/170 solved 0% complete

Simulation and Parsing

What is this?

There is nothing to discover in these problems. The rules are printed in the question, and the entire task is implementing them exactly — which turns out to be harder than it sounds, because each one contains a case that is easy to overlook and produces an answer that is almost right.

Version 1.0 versus 1.0.0. A snapshot update where a cell must not see its neighbour's new value. A read that must remember characters left over from last time. In every case, enumerating the cases before writing is what separates a clean answer from a patched one.

flowchart TD A["the rule is given"] --> B["what makes it awkward?"] B --> C["in-place update needs the OLD values
→ encode both states in one integer"] B --> D["inputs of unequal shape
→ treat missing parts as zero"] B --> E["output alignment from the RIGHT
→ build backwards, then reverse"] B --> F["state must survive between calls
→ keep a leftover buffer"]

💡 Fun fact: Read N Characters Given Read4 II is the version with multiple calls, and it is a genuinely different problem from the single-call one. read4 always fetches four characters, so a call asking for 5 will over-read by 3 — and those 3 characters belong to the next call. The buffer holding them must therefore live in the object, not the function. Anyone who has written a network or file reader has met this exact bug, which is why the problem is asked at all.

🔓 The 5 problems in this chapter are free. Sign in with Google or Microsoft to start solving.


The one-line idea: find the awkward part of the specification and choose a representation that dissolves it — two bits per cell for a simultaneous update, implicit zeros for missing segments, a persistent buffer for leftover input.


1. Encode two states in one value

Game of Life updates every cell simultaneously, so a cell must see its neighbours' old values. Rather than copying the grid, keep the old state in bit 0 and write the new state into bit 1:

for r in range(m):
    for c in range(n):
        live = count_live_neighbours(board, r, c)      # reads bit 0 only
        if board[r][c] & 1:                            # currently alive
            if live in (2, 3):
                board[r][c] |= 2                       # stays alive
        elif live == 3:
            board[r][c] |= 2                           # becomes alive

for r in range(m):
    for c in range(n):
        board[r][c] >>= 1                              # new state becomes current

Counting neighbours with & 1 always reads the old value, whether or not that neighbour has been processed. The second pass is a shift. No second grid, and the rule stays readable.

2. Missing means zero

def compare_version(v1, v2):
    a, b = v1.split('.'), v2.split('.')
    for i in range(max(len(a), len(b))):
        x = int(a[i]) if i < len(a) else 0             # missing segment = 0
        y = int(b[i]) if i < len(b) else 0
        if x != y:
            return 1 if x > y else -1
    return 0

Iterating to the longer length and defaulting to zero is what makes 1.0 equal 1.0.0. And int() handles leading zeros for free, which is why comparing the strings directly would be wrong.

3. Build backwards when alignment is from the right

Licence key reformatting groups characters from the end, so the first group may be short. Building the result in reverse and reversing at the end avoids computing where the short group starts:

cleaned = s.replace('-', '').upper()
out = []
for i, ch in enumerate(reversed(cleaned)):
    if i and i % k == 0:
        out.append('-')
    out.append(ch)
return ''.join(reversed(out))

4. A 30-second worked example (Buddy Strings)

Can s become goal with exactly one swap?

s = "ab",   goal = "ba"    differences at 0 and 1, and they cross-match → True
s = "ab",   goal = "ab"    zero differences → need a REPEATED character
                            "ab" has none → False
s = "aa",   goal = "aa"    zero differences, but 'a' repeats
                            → swap the two a's → True  ✅
s = "abc",  goal = "abd"   exactly one difference → impossible → False

The third case is the one that catches people. Equal strings are only buddies if some character appears twice, because the swap must actually happen and must leave the string unchanged.


5. Where you'll actually meet this

  • Release and dependency tooling. Semantic version comparison with unequal segment counts, in every package manager.
  • Payment and identifier display. Right-aligned grouping of card numbers, licence keys and IBANs.
  • Cellular automata and simulation. Any simultaneous-update rule on a grid, from Conway to physics steps.
  • I/O and network layers. Buffered reads where a fixed-size fetch over-reads and the remainder must persist.
  • Diff and comparison tools. Character-level difference detection with precise case handling.

6. Problems in this chapter

▶ Game of Life

Update a grid in place under simultaneous-update rules. Two bits per cell, then a shift pass.
Pattern: in-place state encoding. Target: O(mn) time, O(1) extra space.

▶ Compare Version Numbers

Compare dotted version strings. Split, iterate to the longer length, treat missing segments as zero.
Pattern: defensive parsing. Target: O(n) time.

▶ License Key Formatting

Regroup a key into blocks of k from the right, uppercased. Build backwards and reverse.
Pattern: right-aligned grouping. Target: O(n) time.

▶ Buddy Strings

Decide whether one swap makes the strings equal. Enumerate: unequal lengths, zero differences with a repeat, exactly two cross-matching differences.
Pattern: exhaustive case analysis. Target: O(n) time, O(1) space.

▶ Read N Characters Given Read4 II

Read n characters using a read4 primitive, across multiple calls. Keep the over-read characters in a persistent buffer.
Pattern: buffered I/O with retained state. Target: O(n) per call.


7. Common pitfalls 🚫

  • Copying the grid in Game of Life. Correct, and it fails the stated in-place requirement.
  • Reading neighbours without masking. & 1 is what keeps the count reading pre-update values.
  • Comparing version strings lexicographically. "10" < "9" as text; parse to integers.
  • Iterating only to the shorter version's length. 1.0 and 1.0.0 must compare equal.
  • Forgetting the repeated-character case in Buddy Strings when the strings are already equal.
  • Making the leftover buffer local in the read problem. It must persist across calls, which is the entire point of part II.
  • Assuming read4 fills the buffer. A short read means end of file, and the loop must stop.

8. Key takeaways

  1. Find the awkward clause and design around it. Simultaneous updates, unequal lengths, retained state.
  2. Two bits per cell turn an in-place simultaneous update into two clean passes.
  3. Missing means zero, not shorter-therefore-smaller.
  4. Build backwards when alignment is from the right, then reverse once.
  5. State that must survive calls belongs to the object, not the function.
  6. Enumerate cases before coding. On Buddy Strings that list is the solution.
  7. Why interviewers use these: no insight can rescue a sloppy implementation, so they show your actual habits — which is precisely what they want to see.

Order: Game of Life → Compare Version Numbers → License Key Formatting → Buddy Strings → Read N Characters Given Read4 II.

Read N Characters Given Read4

Core idea: You're given a low-level primitive — read4(buf4) — that grabs up to four characters from a file into a fixed 4-slot buffer and tells you how many it actually got. Your job is to build a higher-level read(buf, n) that fills the caller's buffer with up to n characters. The whole solution is a single loop: call read4 into a small scratch buffer, then copy only min(got, remaining) of those characters into the destination, and stop the moment read4 returns fewer than 4 (that's end-of-file) or you've already delivered n. The two subtleties everyone trips on are the partial last chunk (you might read 4 but only need 1) and the short read (EOF signalled by a count below 4).


Problem, rephrased

Forget the LeetCode wording for a second. Here's the scenario:

You're writing a driver for a sensor that streams bytes over a fixed-width link. The hardware exposes exactly one call: "hand me your 4-slot buffer, I'll stuff in as many bytes as are ready (up to 4) and return the count." That's read4. Upstream code, though, wants to say "give me the next n bytes" without caring about the 4-byte transfer width. So you wrap the primitive: keep calling read4, accumulate what it returns into the caller's buffer, and quit as soon as either the sensor runs dry (a transfer of fewer than 4 means it had nothing more to give) or you've collected the n the caller asked for.

That's the problem: given a read4(buf4) primitive that reads up to 4 characters into a 4-char buffer and returns the count read (LeetCode 157), implement read(buf, n) that reads up to n characters into buf and returns how many it actually read.

File contents n Returned buf after Why
"abc" 4 3 a b c only 3 chars exist; first read4 returns 3 (< 4) → EOF, stop
"abcde" 5 5 a b c d e read4→4 (copy 4), read4→1 (copy 1), total 5 = n, stop
"abcdABCD1234" 12 12 a b c d A B C D 1 2 3 4 three full read4 calls of 4 each, total 12 = n

The return value is the count actually read, which is min(n, file length).


Sign in to continue reading

The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.

Sign in to read the full lesson

Sign in to MAANG.io

Use your Google or Microsoft account — no password to remember.

Continue with Google Continue with Microsoft

Please accept the terms above to continue.