Decode String
Problem
k[body], meaning body
written out k times; groups may be nested and mixed with plain letters.
Given a valid encoded string s, return the fully expanded string.3[a]2[bc]Outputaaabcbc3[a2[c]]Outputaccaccacc2[abc]3[cd]efOutputabcabccdcdcdef1 ≤ |s| ≤ 30 · lowercase letters, digits, [ ] · 1 ≤ k ≤ 300 · output length ≤ 105 · input always valid
Try it yourself
class Solution {
public String decodeString(String s) {
}
}
[ opens a new “context” that must be finished before
the outer one can continue. Save the outer text and its k on [, and on
] glue k copies of the inner text back onto it — recursion or a stack.
(Below: L = output length, d = nesting depth.)Instinct If the answer for n items can be built from the same problem on halves (or smaller pieces) plus a cheap combine step - split, recurse, merge.
Memory hack Split it, trust it, stitch it - nail the base case, take the leap of faith on smaller calls, then glue their answers together.
Open the Recursion & Divide and Conquer guide →Approaches
Brute ForceExpand Innermost Group Repeatedly
O(p·(n+L))Space O(n+L)The first ] and the last [ before it form an
innermost group whose body is letters only. Replace k[body] with
body × k and repeat until no brackets remain. Each of the p
expansions rebuilds the whole string.
public String decodeString(String s) { int close; while ((close = s.indexOf(']')) != -1) { int open = s.lastIndexOf('[', close); // innermost pair int start = open; while (start > 0 && Character.isDigit(s.charAt(start - 1))) start--; int k = Integer.parseInt(s.substring(start, open)); String body = s.substring(open + 1, close).repeat(k); s = s.substring(0, start) + body + s.substring(close + 1); } return s; }
Simple string surgery, but re-copies the entire string once per bracket pair.
BetterRecursive Descent
O(n + d·L)Space O(n+L)decode() reads letters until it hits ] or the end. On a
number it parses k, skips [, recursively decodes the
inner part, skips ] and appends it k times. A shared index
means every input character is read once.
private int i; public String decodeString(String s) { i = 0; return decode(s); } private String decode(String s) { StringBuilder sb = new StringBuilder(); while (i < s.length() && s.charAt(i) != ']') { char c = s.charAt(i); if (Character.isDigit(c)) { int k = 0; while (Character.isDigit(s.charAt(i))) k = k * 10 + (s.charAt(i++) - '0'); i++; // skip '[' String inner = decode(s); i++; // skip ']' sb.append(inner.repeat(k)); } else { sb.append(c); i++; } } return sb.toString(); }
Clean single scan; recursion depth equals nesting depth.
OptimalTwo Stacks (Iterative)
O(n + d·L)Space O(n+L)Simulate the recursion explicitly. On [ push the pending count and
the text built so far, then start fresh. On ] pop both and append
k copies of the current text to the popped builder. Pushing the
builder (not a String copy) keeps appends amortised O(1).
public String decodeString(String s) { Deque<Integer> counts = new ArrayDeque<>(); Deque<StringBuilder> texts = new ArrayDeque<>(); StringBuilder cur = new StringBuilder(); int k = 0; for (char c : s.toCharArray()) { if (Character.isDigit(c)) { k = k * 10 + (c - '0'); } else if (c == '[') { counts.push(k); texts.push(cur); cur = new StringBuilder(); k = 0; } else if (c == ']') { StringBuilder prev = texts.pop(); prev.append(cur.toString().repeat(counts.pop())); cur = prev; } else { cur.append(c); } } return cur.toString(); }
Dry run3[a2[c]]
- i=13[ia2[c]]push(3, ""), cur=""
- i=23[ai2[c]]cur="a"
- i=43[a2[ic]]push(2, "a"), cur=""
- i=53[a2[ci]]cur="c"
- i=63[a2[c]i]cur="a"+2×"c"="acc"
- i=73[a2[c]]i""+3×"acc" ✔
Same cost as recursion, no call-stack risk — the usual interview answer.
12[a]) are the classic trap — always accumulate
k = k*10 + digit and reset it after pushing.At a glance
| Level | Approach | Time | Space |
|---|---|---|---|
| Brute Force | Expand Innermost Group Repeatedly | O(p·(n+L)) | O(n+L) |
| Better | Recursive Descent | O(n + d·L) | O(n+L) |
| Optimal | Two Stacks (Iterative) | O(n + d·L) | O(n+L) |
- Reading only one digit of
k— breaks on10[a]or300[x]. - Forgetting to reset
k/curafter[. - Pushing
cur.toString()and rebuilding the prefix on every]— quadratic copying.
- Say it is a grammar:
str := (letter | k[str])*— recursion falls out of it. - Mention complexity depends on output length, not just
|s|. - Trace
3[a2[c]]aloud, showing what each stack holds.