Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Given A = merge(reverse(s), shuffle(s)), each character of s appears twice in A, so s needs exactly count[c]/2 of each character c. The recovered s read in A-order is a subsequence of reverse(A); to get the lexicographically smallest s, scan A from RIGHT to LEFT and greedily build s with a monotonic stack: take a needed character, but first pop any larger characters already on the stack provided enough copies remain to the left to re-collect them later. O(n) time, O(1) space (26-letter counts).
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you can peel a scary 'reverse + shuffle + merge' wrapper down to 'smallest subsequence of reverse(A) with fixed per-character counts', and then implement the count-aware monotonic-stack greedy correctly.
Where candidates slip
Building s left-to-right (which yields the wrong order — try "aabb"), or popping a larger top without checking that enough copies remain to the left to re-collect it.
How to stand out
Derive the half-count fact, explain why scanning A right-to-left makes the stack equal s, and state the precise pop condition: discard a bigger top only when need[top] - used[top] < remaining[top].
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachExplore the full treeCollapse the tree
How to read it:The merge/shuffle/reverse wrapper reduces to: smallest subsequence of reverse(A) with half-counts.
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Brute Force — Search All Valid Subsequences
1Hint 1Explore hintHide hint
What exactly makes a string s valid here? It must use need[c] copies of each character and be a subsequence of reverse(A).
2Hint 2Explore hintHide hint
Enumerate subsequences of reverse(A): at each position either take the character (if still needed) or skip it.
3Hint 3Explore hintHide hint
Keep the lexicographically smallest completed candidate. This explores up to 2ⁿ branches — correct but exponential.
A · Core idea
Make the validity condition explicit, then search. A valid s uses exactly need[c] = count[c]/2 of each character and must be a subsequence of reverse(A) (because the recovered characters, read in A's order, form reverse(s)). So walk reverse(A) and at every character decide to take it (only if that character is still needed) or skip it. Whenever a complete candidate of the right length is formed, keep it if it's smaller than the best so far.
Why it's correct. It literally enumerates every count-valid subsequence of reverse(A) and returns the minimum, so it cannot miss the answer.
Why it's hopeless at scale. With |A| up to 10⁴, the take/skip tree has up to 2ⁿ leaves. Even aggressive pruning (stop when a count is exhausted, abandon branches already larger than the best) leaves it exponential in the worst case.
Bridge to optimal. The exponential search is really just trying to make each output position as small as possible while keeping the remaining counts satisfiable. A monotonic stack performs exactly that local decision greedily in a single pass — pop a larger committed character whenever enough copies remain to the left to re-collect it.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
A = "aabb"Array state
take b
path = "b"
baC · Code with teaching comments
Reference implementation previewpublic String reverseShuffleMerge(String s) { int[] count = new int[26]; for (int i = 0; i < s.length(); i++) count[s.charAt(i) - 'a']++; int[] need = new int[26]; int target = 0;Explore the full codeHide the full code
D · Why it works and complexityO(2ⁿ) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
The brute force makes the validity rule precise — s uses half-counts and is a subsequence of reverse(A). Once you see the answer is 'smallest count-valid subsequence', the greedy stack is the standard O(n) realisation of that exact goal.
ComplexityO(2ⁿ) time, O(n) space — Each of the n positions of reverse(A) is taken or skipped, giving up to 2ⁿ branches; each completed candidate costs O(n) to compare. Exponential time, O(n) recursion depth.
E · Edge cases and pitfalls3Searching subsequences of A instead of reverse(A) — yields reverse(s), not s.Explore edge casesHide edge cases
Pitfalls
- Searching subsequences of A instead of reverse(A) — yields reverse(s), not s.
- Forgetting the per-character need cap, producing wrong-multiset candidates.
- Expecting it to finish for large A — it is exponential.
Approach 2 of 2 · Optimal
Greedy Monotonic Stack over reverse(A) (Optimal)
1Hint 1Explore hintHide hint
You want each output character as small as possible, as early as possible, while keeping the remaining counts satisfiable. What structure supports 'undo a committed bigger choice'?
2Hint 2Explore hintHide hint
Scan A right-to-left. Track need[c], used[c] (on the stack) and remaining[c] (still to the left). Only take a character while used[c] < need[c].
3Hint 3Explore hintHide hint
Before pushing c, pop any stack-top larger than c — but only if you can still re-collect it later: need[top] - used[top] < remaining[top]. Then push c.
A · Core idea
Scan A from right to left (i.e. left-to-right over reverse(A)), building s on a stack. Maintain three 26-slot arrays: need[c] = count[c]/2, used[c] (how many c's are currently on the stack), and remaining[c] (how many c's are still unprocessed, to the left).
Per character `c` (after decrementing `remaining[c]`):
- If
used[c] == need[c], we already have all thec's we need — skip it (taking more would exceed the multiset). - Otherwise, before pushing, pop any stack-top
tthat is larger thancprovidedneed[t] - used[t] < remaining[t]. That condition means: even after removing thistfrom the stack, enought's remain to the left to re-collect later and still meetneed[t]. Popping a larger character that sits beforecmakes the result lexicographically smaller. - Then push
cand incrementused[c].
Why right-to-left. The recovered characters, read in A's natural order, spell reverse(s). Reading A backwards is reading reverse(A) forwards, so the stack we accumulate spells s directly — no final reversal. The "aabb" → "ba" case is the proof: a left-to-right greedy would wrongly emit "ab".
Why the pop guard is exactly `need[t] - used[t] < remaining[t]`. When we consider popping t, used[t] still counts the copy we're about to remove. After removal we'd need need[t] - (used[t]-1) more t's, which must be available among the remaining[t] unprocessed copies: remaining[t] >= need[t] - used[t] + 1, i.e. need[t] - used[t] < remaining[t]. If that fails, this t is irreplaceable — keep it and stop popping.
Why each character is pushed/popped at most once gives the linear bound — the inner while-loop is amortised O(1).
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
A = "eggegg"Array state
after g, g
stack = [g, g]
eggC · Code with teaching comments
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
This is the count-constrained cousin of 'remove k digits to make the smallest number'. The standard monotonic stack greedily discards a larger earlier character whenever something smaller arrives — here the extra twist is a feasibility guard (enough copies remain) and a take-cap (don't exceed need[c]). Scanning the reversed string is the move that makes the stack come out as s.
ComplexityO(n) time, O(1) space — Each character is pushed and popped at most once across the single backward pass → O(n) total. The counts, used, and remaining arrays are 26 entries regardless of n → O(1) space.
E · Edge cases and pitfalls8A = "aabb" — reverse(A)="bbaa". Take b; at 'a', b>a but remaining b is 0 so need-used(0) < 0 is false → can't pop → push a → "ba". Shows why the pop guard matters.Explore edge casesHide edge cases
Edge cases handled
A = "aabb"reverse(A)="bbaa". Take b; at 'a', b>a but remaining b is 0 so need-used(0) < 0 is false → can't pop → push a → "ba". Shows why the pop guard matters.A = "abab"reverse="baba". Take b; at 'a', another b remains so pop b, push a; later push b → "ab".A = "aa"need a:1 → single character → "a".A = "eggegg"Classic case → "egg" (pops both g's when the e arrives).
Pitfalls
- Scanning A left-to-right instead of right-to-left — produces the wrong order (fails "aabb").
- Popping a larger top without the remaining-copies guard — can leave a character's quota unfillable.
- Decrementing remaining at the wrong time — it must drop the moment the character is read.
- Taking a character past its need quota, corrupting the multiset.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic String reverseShuffleMerge(String s) { int[] count = new int[26]; for (int i = 0; i < s.length(); i++) count[s.charAt(i) - 'a']++; int[] need = new int[26]; for (int i = 0; i < 26; i++) need[i] = count[i] / 2;Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Scanning in the wrong direction
s is a subsequence of reverse(A). Scanning A forward builds reverse(s); "aabb" would wrongly produce "ab" instead of the correct "ba".
Explore the code comparisonHide the code comparison
Scanning in the wrong direction
s is a subsequence of reverse(A). Scanning A forward builds reverse(s); "aabb" would wrongly produce "ab" instead of the correct "ba".
Don't write this
for ch in s: # forward over A — wrong order
...Write this instead
for ch in reversed(s): # forward over reverse(A) — stack spells s
...2Popping without the feasibility guard
Discarding a larger top is only safe if enough of that character remains to the left to satisfy its need later; otherwise its quota becomes impossible to fill.
Explore the code comparisonHide the code comparison
Popping without the feasibility guard
Discarding a larger top is only safe if enough of that character remains to the left to satisfy its need later; otherwise its quota becomes impossible to fill.
Don't write this
while stack and stack[-1] > ch:
used[stack[-1]] -= 1
stack.pop()Write this instead
while stack and stack[-1] > ch and need[stack[-1]] - used[stack[-1]] < remaining[stack[-1]]:
used[stack[-1]] -= 1
stack.pop()3Exceeding the required count
s contains exactly count[c]/2 of each character. Taking more than need[c] corrupts the multiset and the answer length.
Explore the code comparisonHide the code comparison
Exceeding the required count
s contains exactly count[c]/2 of each character. Taking more than need[c] corrupts the multiset and the answer length.
Don't write this
// always take the character
stack.append((char) (c + 'a'));
used[c]++;Write this instead
if (used[c] >= need[c]) continue;
stack.append((char) (c + 'a'));
used[c]++;Revise
Carry the pattern into the interview
Pattern to remember
Greedy smallest subsequence with required counts (scan reversed, monotonic stack)
Code pattern to memorize
need[c] = count(A, c) // 2
scan A right-to-left:
remaining[c] -= 1
if used[c] < need[c]:
while stack and stack[-1] > c and need[top]-used[top] < remaining[top]:
used[top] -= 1; stack.pop()
stack.push(c); used[c] += 1
return stack joinedRules to remember
- 1Each character appears an even number of times in A; s uses exactly half of each.
- 2Scan A right-to-left — that makes the stack you build equal to s directly (it walks reverse(A)).
- 3Only take a character while you still need it:
used[c] < need[c]. - 4Pop a larger stack-top only if enough of it remains to the left to re-collect later:
need[top] - used[top] < remaining[top]. - 5Decrement
remaining[c]the instant you read c, before deciding to take or pop.
Use this pattern when
- Recover the lexicographically smallest string under a merge/shuffle/reverse construction
- Build the smallest subsequence that uses an exact multiset of characters
- Greedy 'remove bigger earlier characters if you can afford to' with a stack
- Counts are fixed and the alphabet is small (a–z)
Choose another pattern when
- No required-count constraint, just smallest k-length subsequence → classic 'remove k digits' stack
- Largest (not smallest) result wanted → flip the comparison and pop smaller tops
- Characters must keep relative positions exactly → not a free greedy, likely DP
- You need ALL valid s, not just the smallest → enumerate, don't greedily prune
Interview takeaway
Strip the construction down to its invariant: s is the smallest subsequence of reverse(A) using half of each character. Scanning A backwards turns the build into a standard monotonic-stack greedy, where a larger top is discarded only when leftover copies guarantee you can rebuild it.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternCount-constrained smallest-subsequence via a monotonic stack. When you must build the lexicographically smallest string using a fixed multiset of characters, greedily push characters and pop larger committed ones whenever enough copies remain to re-collect them. The construction-specific twist here is recognising s is a subsequence of reverse(A), so you scan A backwards.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Remove K DigitsComing soonThe base monotonic-stack greedy for the smallest number — no per-character count cap.
- Remove Duplicate LettersComing soonSmallest subsequence with each letter once — same 'pop if it reappears later' guard.
- Smallest Subsequence of Distinct CharactersComing soonDuplicate of Remove Duplicate Letters — identical greedy stack.
- Create Maximum NumberComing soonMonotonic stack to pick the best subsequence, then merge — a harder cousin.