String Processing
What is this?
Every problem here is a single left-to-right pass where the stack holds what is still open, or still liable to be cancelled. Adjacent duplicates cancel; unmatched brackets are removed; nested groups score; a Lisp expression's scopes stack up and unwind. In each case you never look backwards through the string — whatever needs revisiting is already sitting on the stack, in the right order.
(char, run length)"] B --> D["Minimum Remove to Make Valid
indices of unmatched '('"] B --> E["Score of Parentheses
score of the enclosing frame"] B --> F["Validity After Substitutions
the partial 'abc' being built"] B --> G["Parse Lisp Expression
scope of variable bindings"]
💡 Fun fact: Score of Parentheses has a solution so short it looks wrong: track the current depth, and every time you see
()add2^depthto the answer. No stack at all. It works because scoring is distributive — the nesting multiplies each innermost pair by a power of two, and summing those contributions independently gives the same total. That is the same "count each atom's contribution" move as the monotonic-stack chapter, and it is worth recognising the family resemblance.
🔓 The 5 problems in this chapter are free. Sign in with Google or Microsoft to start solving.
The one-line idea: push what is unresolved, pop when it resolves, and the stack's contents at the end are the answer — the survivors, the unmatched positions, or the accumulated score.
1. Push the pair, not the character
Removing k adjacent duplicates naively means re-scanning after each removal, which is quadratic. Push (character, run length) instead:
s = "deeedbbcccbdaa", k = 3
d → [(d,1)]
e,e,e → [(d,1),(e,3)] → run hits 3, pop → [(d,1)]
d → [(d,1),(d,1)] → merge into (d,2) → [(d,2)]
b,b → [(d,2),(b,2)]
c,c,c → [(d,2),(b,2),(c,3)] → pop → [(d,2),(b,2)]
b → (b,2) becomes (b,3) → pop → [(d,2)]
d → (d,2) becomes (d,3) → pop → []
a,a → [(a,2)]
result: "aa"
The cascade is the point. Removing ccc brings a b next to bb, which removes and brings a d next to dd. Counting on the stack makes each cascade automatic rather than requiring another pass.
2. The stack as a record of what to delete
Minimum Remove to Make Valid Parentheses does not build the answer on the stack — it collects indices to remove. Push the index of every (; on a ), pop if the stack is non-empty, otherwise mark this ) as unmatched. Whatever indices remain on the stack at the end are unmatched (. Delete both sets in one final pass.
This inversion — using the stack to record failures rather than successes — is worth having in your repertoire.
3. Scopes and bindings
Parse Lisp Expression is the hardest problem in the chapter, and the stack holds scopes. A let introduces bindings that shadow outer ones and must disappear when its expression closes. The clean structure is a stack of dictionaries: look up a variable by scanning from the innermost scope outward, and pop the whole frame when the expression ends. That is exactly how a real interpreter handles lexical scope.
4. Where you'll actually meet this
- Editors and IDEs. Bracket matching, auto-close, and "highlight the unmatched paren" are this exact scan.
- Linters and formatters. Detecting and repairing unbalanced delimiters, and computing nesting depth for indentation.
- Interpreters. Scope chains for variable lookup are a stack of dictionaries, precisely as above.
- Text sanitisation. Collapsing repeated characters and stripping malformed markup before rendering user content.
- Protocol and log parsing. Nested framing formats unwind with the same push/pop discipline.
5. Problems in this chapter
▶ Score of Parentheses
Score a balanced string where () is 1, AB is A+B, and (A) is 2*A. Either stack the enclosing frame's score, or use the depth trick and sum 2^depth per innermost pair.
Pattern: stack of frame scores (or depth contribution). Target: O(n) time, O(n) or O(1) space.
▶ Minimum Remove to Make Valid Parentheses
Remove the fewest characters to balance the string. Stack the indices of unmatched (, mark unmatched ) as you go, delete both sets at the end.
Pattern: stack of failure positions. Target: O(n) time, O(n) space.
▶ Remove All Adjacent Duplicates in String II
Delete every run of exactly k equal adjacent characters, repeatedly. Push (char, count) so cascades resolve in one pass.
Pattern: stack of (value, run length). Target: O(n) time, O(n) space.
▶ Check If Word Is Valid After Substitutions
Decide whether a string can be built by repeatedly inserting "abc". Push characters and pop a completed abc whenever a c closes one.
Pattern: stack as a partial-match buffer. Target: O(n) time, O(n) space.
▶ Parse Lisp Expression
Evaluate a small Lisp with let, add and mult. A stack of scopes handles shadowing and cleanup.
Pattern: scope stack + recursive evaluation. Target: O(n) time, O(depth) space.
6. Common pitfalls 🚫
- Rescanning after each removal. Cascades must be handled by the stack, not by looping until the string stops changing — that is O(n²) at best.
- Storing characters without counts. Runs need lengths; a character-only stack forces re-counting.
- Building the result string inside the loop. Collect on the stack and join once at the end.
- Deleting by index while iterating. Collect the indices, then build the survivors in a single pass — mutating the string as you go invalidates every later index.
- Forgetting leftover unmatched
(. They are still on the stack when the scan ends and must be removed too. - Assuming
2^depthis safe for arbitrary input. For Score of Parentheses the depth is bounded by the problem, but say so rather than leaving it implicit. - Letting
letbindings leak. In the Lisp evaluator, a scope must be popped when its expression ends, or an inner binding silently shadows outward.
7. Key takeaways
- One pass, and the stack holds the unresolved. Everything you might have to revisit is already at hand, in order.
- Push pairs when runs matter.
(char, count)makes cascading deletions automatic. - A stack can record what to remove, not only what to keep.
- Scopes are a stack of dictionaries — the standard way real interpreters implement lexical scope.
- Look for a distributive shortcut. When a score decomposes per atom, depth arithmetic can replace the stack entirely.
- Collect, then build. Never mutate the string mid-scan.
- Why interviewers like it: these look like string puzzles and are really tests of whether you can spot the "open until resolved" structure and handle every cascade correctly in one pass.
Order: Score of Parentheses → Minimum Remove to Make Valid Parentheses → Remove All Adjacent Duplicates in String II → Check If Word Is Valid After Substitutions → Parse Lisp Expression.
Remove All Adjacent Duplicates in String II
Core idea: Don't store characters one box at a time — store runs. Keep a stack of
[char, count]pairs. Each new character either extends the top run's count or starts a new run, and the moment a count hitskthat whole run vanishes inO(1). The carried count is what lets a freshly exposed neighbor keep growing without you ever rescanning the string.
Problem, rephrased
You're tidying up a string s under one rule: wherever k identical characters sit next to each other, delete all k of them. Deleting a run can shove two previously-separated runs of the same character into contact — and if their combined length now reaches k, they go too. You keep applying the rule until no run of k equal adjacent characters survives, then return what's left.
The deletions cascade. Peel away an inner run and the characters that were straddling it may suddenly become neighbors, triggering a fresh removal. The answer is the unique string you reach once the dust settles — and it's unique no matter what order you remove runs in.
Input s |
k |
Output | Why |
|---|---|---|---|
"abcd" |
2 |
"abcd" |
no character repeats 2 in a row |
"deeedbbcccbdaa" |
3 |
"aa" |
eee→gone, ccc→gone exposes bb·b→bbb→gone, then d·d→dd… cascades to aa |
"pbbcggttciiippooaais" |
2 |
"ps" |
every doubled run collapses; survivors are the lone p and s |
"aaa" |
1 |
"" |
k = 1 means every character is its own length-1 run → all deleted |
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