394. Decode String
Problem Description
This problem asks you to decode a string that has been encoded with a specific pattern. The encoding follows the rule k[encoded_string]
, where k
is a positive integer that indicates how many times the encoded_string
inside the brackets should be repeated.
For example:
"3[a]"
would decode to"aaa"
(the letter 'a' repeated 3 times)"2[abc]"
would decode to"abcabc"
(the string 'abc' repeated 2 times)"3[a2[c]]"
would decode to"accaccacc"
(first decode the inner2[c]
to get"cc"
, then3[acc]
becomes"accaccacc"
)
The encoding can be nested, meaning you can have brackets inside brackets. When decoding, you need to process from the innermost brackets outward.
Key constraints:
- The input string is always valid with properly matched brackets
- Numbers only appear before brackets to indicate repetition count
- Letters in the original data are only lowercase English letters
- The digit
k
can be multi-digit (e.g.,"10[a]"
means repeat 'a' 10 times)
The solution uses two stacks to handle the nested structure:
s1
stack stores the repetition numberss2
stack stores the accumulated strings before each opening bracket- When encountering
'['
, push the current number and result string to their respective stacks and reset them - When encountering
']'
, pop from both stacks and construct the decoded substring by repeating the current result and prepending what was stored - Regular letters are simply appended to the current result string
Intuition
The key insight is recognizing that this problem has a nested structure similar to parentheses matching. When we see a pattern like 3[a2[c]]
, we need to decode from the inside out - first handle 2[c]
to get cc
, then handle 3[acc]
to get the final result.
This naturally suggests using a stack-based approach. Why? Because stacks are perfect for handling nested structures where we need to "pause" our current work, handle something else (the inner bracket), and then "resume" where we left off.
Think about what happens when we're building a string and encounter an opening bracket [
:
- We need to "save" our current progress (the string we've built so far)
- We need to "save" the number that came before this bracket
- We need to start fresh to build the string inside the brackets
Similarly, when we hit a closing bracket ]
:
- We need to take what we just built inside the brackets
- Repeat it the appropriate number of times
- Combine it with whatever we had built before entering these brackets
This pause-and-resume pattern is exactly what stacks excel at. We need two pieces of information at each nesting level: the repetition count and the string built before that level. That's why we use two stacks - one for numbers (s1
) and one for strings (s2
).
The algorithm mimics how we would manually decode the string: read character by character, accumulate digits into numbers, accumulate letters into strings, and use the bracket boundaries to know when to apply the repetition logic. The stacks act as our "memory" to keep track of multiple levels of nesting.
Solution Approach
Let's walk through the implementation step by step:
Data Structures:
- Two stacks:
s1
for storing repetition counts ands2
for storing accumulated strings - Two variables:
num
to build multi-digit numbers andres
to build the current string segment
Algorithm:
-
Initialize variables:
s1 = []
(stack for numbers)s2 = []
(stack for strings)num = 0
(accumulator for multi-digit numbers)res = ''
(current result string being built)
-
Process each character
c
in the input string:Case 1: Digit character
if c.isdigit(): num = num * 10 + int(c)
Build multi-digit numbers by multiplying the existing
num
by 10 and adding the new digit. For example, if we see "23", we first getnum = 2
, thennum = 2*10 + 3 = 23
.Case 2: Opening bracket
'['
elif c == '[': s1.append(num) s2.append(res) num, res = 0, ''
Save the current state before entering a new nesting level:
- Push
num
(the repetition count) ontos1
- Push
res
(the string built so far) ontos2
- Reset both
num
andres
to start fresh for the content inside brackets
Case 3: Closing bracket
']'
elif c == ']': res = s2.pop() + res * s1.pop()
Complete the current nesting level:
- Pop the repetition count from
s1
- Pop the previous string from
s2
- Repeat the current
res
string by the popped count - Prepend the previous string to get the combined result
Case 4: Letter character
else: res += c
Simply append the letter to the current result string.
- Push
-
Return the final result: After processing all characters,
res
contains the fully decoded string.
Example Walkthrough with "3[a2[c]]"
:
- Read
'3'
:num = 3
- Read
'['
: Push3
tos1
, push''
tos2
, resetnum = 0
,res = ''
- Read
'a'
:res = 'a'
- Read
'2'
:num = 2
- Read
'['
: Push2
tos1
, push'a'
tos2
, resetnum = 0
,res = ''
- Read
'c'
:res = 'c'
- Read
']'
:res = 'a' + 'c' * 2 = 'acc'
(pop from stacks) - Read
']'
:res = '' + 'acc' * 3 = 'accaccacc'
(pop from stacks) - Return
'accaccacc'
The beauty of this approach is that it handles arbitrary nesting depth naturally through the stack mechanism, processing one level at a time from innermost to outermost.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through the algorithm with the input "2[a3[b]]"
:
Initial State:
s1 = []
(number stack)s2 = []
(string stack)num = 0
res = ''
Step-by-step execution:
-
Read '2': It's a digit
num = 0 * 10 + 2 = 2
- State:
num=2, res='', s1=[], s2=[]
-
Read '[': Opening bracket - save current state and reset
- Push
num=2
tos1
→s1=[2]
- Push
res=''
tos2
→s2=['']
- Reset:
num=0, res=''
- State:
num=0, res='', s1=[2], s2=['']
- Push
-
Read 'a': It's a letter
res = res + 'a' = 'a'
- State:
num=0, res='a', s1=[2], s2=['']
-
Read '3': It's a digit
num = 0 * 10 + 3 = 3
- State:
num=3, res='a', s1=[2], s2=['']
-
Read '[': Opening bracket - save current state and reset
- Push
num=3
tos1
→s1=[2,3]
- Push
res='a'
tos2
→s2=['','a']
- Reset:
num=0, res=''
- State:
num=0, res='', s1=[2,3], s2=['','a']
- Push
-
Read 'b': It's a letter
res = res + 'b' = 'b'
- State:
num=0, res='b', s1=[2,3], s2=['','a']
-
Read ']': Closing bracket - combine with previous level
- Pop from
s1
:3
- Pop from
s2
:'a'
res = 'a' + 'b' * 3 = 'a' + 'bbb' = 'abbb'
- State:
num=0, res='abbb', s1=[2], s2=['']
- Pop from
-
Read ']': Closing bracket - combine with previous level
- Pop from
s1
:2
- Pop from
s2
:''
res = '' + 'abbb' * 2 = 'abbbabbb'
- State:
num=0, res='abbbabbb', s1=[], s2=[]
- Pop from
Final Result: 'abbbabbb'
The algorithm correctly decodes the nested structure by:
- Building the innermost content first (
'b'
) - Repeating it 3 times and combining with the previous level (
'abbb'
) - Finally repeating the entire result 2 times (
'abbbabbb'
)
Solution Implementation
1class Solution:
2 def decodeString(self, s: str) -> str:
3 # Stack to store repetition counts
4 count_stack = []
5 # Stack to store intermediate string results
6 string_stack = []
7 # Current number being parsed (can be multi-digit)
8 current_num = 0
9 # Current string being built
10 current_string = ''
11
12 for char in s:
13 if char.isdigit():
14 # Build multi-digit number (e.g., "30" -> 30)
15 current_num = current_num * 10 + int(char)
16 elif char == '[':
17 # Opening bracket: push current state to stacks and reset
18 count_stack.append(current_num)
19 string_stack.append(current_string)
20 current_num = 0
21 current_string = ''
22 elif char == ']':
23 # Closing bracket: pop from stacks and build result
24 # Pop the string before this bracket group
25 prev_string = string_stack.pop()
26 # Pop the repetition count for this bracket group
27 repeat_count = count_stack.pop()
28 # Combine: previous string + (current string repeated n times)
29 current_string = prev_string + current_string * repeat_count
30 else:
31 # Regular character: append to current string
32 current_string += char
33
34 return current_string
35
1class Solution {
2 public String decodeString(String s) {
3 // Stack to store the repeat counts for nested patterns
4 Deque<Integer> countStack = new ArrayDeque<>();
5 // Stack to store the accumulated strings before entering a bracket
6 Deque<String> stringStack = new ArrayDeque<>();
7
8 int currentCount = 0;
9 String currentString = "";
10
11 // Process each character in the input string
12 for (char ch : s.toCharArray()) {
13 if (Character.isDigit(ch)) {
14 // Build the number (handle multi-digit numbers)
15 currentCount = currentCount * 10 + (ch - '0');
16 } else if (ch == '[') {
17 // Opening bracket: push current state to stacks and reset
18 countStack.push(currentCount);
19 stringStack.push(currentString);
20 currentCount = 0;
21 currentString = "";
22 } else if (ch == ']') {
23 // Closing bracket: pop from stacks and construct repeated string
24 int repeatTimes = countStack.pop();
25 StringBuilder repeatedString = new StringBuilder();
26
27 // Repeat the current string based on the count
28 for (int i = 0; i < repeatTimes; i++) {
29 repeatedString.append(currentString);
30 }
31
32 // Combine with the previous string from the stack
33 currentString = stringStack.pop() + repeatedString.toString();
34 } else {
35 // Regular character: append to current string
36 currentString += ch;
37 }
38 }
39
40 return currentString;
41 }
42}
43
1/**
2 * Decodes an encoded string containing patterns like "k[encoded_string]"
3 * where k is the repeat count and encoded_string is the pattern to repeat
4 * @param s - The encoded string to decode
5 * @return The decoded string
6 */
7string decodeString(string s) {
8 // Current string being built
9 string currentString = "";
10
11 // Stack to store previous string context and repeat count when entering brackets
12 // Each element is a pair: {previousString, repeatCount}
13 stack<pair<string, int>> stk;
14
15 // Current repeat count being parsed
16 int repeatCount = 0;
17
18 // Iterate through each character in the input string
19 for (char c : s) {
20 if (isdigit(c)) {
21 // Build multi-digit numbers (e.g., "23" becomes 23)
22 repeatCount = repeatCount * 10 + (c - '0');
23 } else if (isalpha(c)) {
24 // Append regular characters to current string
25 currentString += c;
26 } else if (c == '[') {
27 // Opening bracket: save current context and reset for nested pattern
28 stk.push({currentString, repeatCount});
29 currentString = "";
30 repeatCount = 0;
31 } else if (c == ']') {
32 // Closing bracket: restore previous context and repeat current pattern
33 auto [previousString, previousRepeatCount] = stk.top();
34 stk.pop();
35 string repeatedString = "";
36 for (int i = 0; i < previousRepeatCount; i++) {
37 repeatedString += currentString;
38 }
39 currentString = previousString + repeatedString;
40 }
41 }
42
43 return currentString;
44}
45
1/**
2 * Decodes an encoded string containing patterns like "k[encoded_string]"
3 * where k is the repeat count and encoded_string is the pattern to repeat
4 * @param s - The encoded string to decode
5 * @returns The decoded string
6 */
7function decodeString(s: string): string {
8 // Current string being built
9 let currentString: string = '';
10
11 // Stack to store previous string context and repeat count when entering brackets
12 // Each element is a tuple: [previousString, repeatCount]
13 const stack: [string, number][] = [];
14
15 // Current repeat count being parsed
16 let repeatCount: number = 0;
17
18 // Iterate through each character in the input string
19 for (const char of s) {
20 if (/[0-9]/.test(char)) {
21 // Build multi-digit numbers (e.g., "23" becomes 23)
22 repeatCount = repeatCount * 10 + Number(char);
23 } else if (/[a-z]/.test(char)) {
24 // Append regular characters to current string
25 currentString += char;
26 } else if (char === '[') {
27 // Opening bracket: save current context and reset for nested pattern
28 stack.push([currentString, repeatCount]);
29 currentString = '';
30 repeatCount = 0;
31 } else if (char === ']') {
32 // Closing bracket: restore previous context and repeat current pattern
33 const [previousString, previousRepeatCount] = stack.pop()!;
34 currentString = previousString + currentString.repeat(previousRepeatCount);
35 }
36 }
37
38 return currentString;
39}
40
Time and Space Complexity
Time Complexity: O(n * m)
where n
is the length of the input string and m
is the maximum length of the decoded string at any intermediate step.
- We iterate through each character in the input string once:
O(n)
- For each
']'
character encountered, we perform string concatenation and multiplication operations - String multiplication
res * s1.pop()
takesO(len(res) * k)
time wherek
is the multiplier - String concatenation
s2.pop() + res * s1.pop()
takesO(len(s2.pop()) + len(res) * k)
time - In the worst case, these operations can result in strings of length proportional to the final output size
- The total output string length can be exponential in the worst case (e.g.,
"2[2[2[a]]]"
produces a string of length 8), but for practical analysis, we considerm
as the maximum intermediate string length
Space Complexity: O(n + m)
where n
is the length of the input string and m
is the maximum length of strings stored in the stacks.
- Stack
s1
stores numbers: at mostO(n)
elements in the worst case - Stack
s2
stores intermediate strings: the total space used is bounded by the sum of lengths of all strings stored - Variable
res
stores the current working string:O(m)
space - Variable
num
usesO(1)
space - In the worst case with nested brackets, the stacks can hold
O(n)
elements, and the strings ins2
can have a combined length ofO(m)
Common Pitfalls
1. Not Handling Multi-Digit Numbers Correctly
The Pitfall:
A common mistake is treating each digit as a separate repetition count instead of building multi-digit numbers. For example, with input "10[a]"
, incorrectly processing would treat '1' and '0' as separate numbers rather than combining them into 10.
Incorrect Implementation:
if char.isdigit():
current_num = int(char) # Wrong! This overwrites instead of accumulating
Correct Solution:
if char.isdigit():
current_num = current_num * 10 + int(char) # Builds multi-digit numbers
2. Forgetting to Reset Variables After Pushing to Stack
The Pitfall:
When encountering an opening bracket '[', forgetting to reset current_num
and current_string
after pushing them to stacks leads to incorrect decoding in nested scenarios.
Incorrect Implementation:
elif char == '[': count_stack.append(current_num) string_stack.append(current_string) # Forgot to reset! This causes values to persist incorrectly
Correct Solution:
elif char == '[': count_stack.append(current_num) string_stack.append(current_string) current_num = 0 # Must reset to 0 current_string = '' # Must reset to empty string
3. Incorrect Order When Building Result on Closing Bracket
The Pitfall: When encountering ']', the order of concatenation matters. A common error is appending instead of prepending the previous string, which produces incorrect results for nested patterns.
Incorrect Implementation:
elif char == ']': # Wrong order - this would put repeated part before the prefix current_string = current_string * count_stack.pop() + string_stack.pop()
Correct Solution:
elif char == ']': # Correct: previous_string + (current_string repeated n times) current_string = string_stack.pop() + current_string * count_stack.pop()
Example to illustrate: For input "2[a3[b]]"
:
- After processing "3[b]", we have
current_string = "bbb"
- When we hit the outer ']', we need:
"a" + "bbb"
repeated 2 times ="abbbabbb"
- Wrong order would give:
"bbb" + "a"
repeated 2 times ="bbbaa"
(incorrect!)
4. Not Initializing current_num to 0
The Pitfall:
Starting with current_num = None
or forgetting to initialize it can cause errors when trying to perform arithmetic operations.
Incorrect Implementation:
def decodeString(self, s: str) -> str:
count_stack = []
string_stack = []
# current_num not initialized or set to None
current_string = ''
Correct Solution:
def decodeString(self, s: str) -> str:
count_stack = []
string_stack = []
current_num = 0 # Must be initialized to 0
current_string = ''
How does merge sort divide the problem into subproblems?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!