Editorial
Core insight
Decode String · Stack Patterns
Core Insight for Decode String
The key intuition for the optimal LeetCode 394 solution is to treat the string parsing as a simulation of a recursive process using an explicit stack.
When we traverse the string and encounter an opening bracket [, we are effectively entering a new "scope" or "frame." We need to remember two things from the current scope before we dive deeper:
- The number of times the upcoming segment should be repeated (
k). - The string we have built so far in the current scope (
currentString).
We push these values onto a stack. When we encounter a closing bracket ], it signals the end of the current scope. We can then pop the previous scope's data from the stack and combine it with the result of the current scope.
This approach enforces the invariant that the top of the stack always contains the context of the immediate parent of the current bracket expression. By maintaining this invariant, we ensure that deeply nested strings are decoded first and correctly appended to their outer layers.
Visual Description:
Imagine traversing the string 3[a2[c]].
- We read
3, then[. We push our current state to the stack and reset. - We read
a, then2, then[. We push the state (holdinga) to the stack and reset again. - We read
c, then]. We resolve the current level: repeatc2 times (cc). We pop the stack to retrievea, appendingccto it (acc). - We read the final
]. We resolve the level: repeatacc3 times.
![]()
Deep dive
Full breakdown
Full reasoning, pitfalls, and implementation flow
LeetCode 394: Decode String Solution & Explanation
Problem Overview
TL;DR: Iterate through the string maintaining a stack to store partial strings and repeat counts, pushing context when entering brackets and popping to combine results when exiting them.
The Decode String problem asks us to expand a compressed string format. The input string contains patterns like k[encoded_string], which signifies that the encoded_string should be repeated exactly k times. These patterns can be nested, such as 3[a2[c]]. Our goal is to return the fully expanded string. This is a popular interview question that tests your ability to handle nested structures and parsing logic.
Brute Force Approach for Decode String
A naive approach to solving LeetCode 394 involves repeatedly scanning the string to resolve the innermost brackets one by one.
The algorithm works as follows:
- Search the string for the first closing bracket
]. - From that position, scan backwards to find the nearest opening bracket
[. - Extract the number
kimmediately preceding the[. - Replace the substring
k[...]with the content inside the brackets repeatedktimes. - Repeat this process until no brackets remain in the string.
Pseudo-code
while string contains '[':
find first ']' at index right
find matching '[' at index left before right
extract number k before left
content = substring(left+1, right)
expanded = repeat content k times
string = string.prefix + expanded + string.suffix
return stringComplexity and Failure Analysis
- Time Complexity: This approach can be highly inefficient. In the worst case, finding brackets and performing string concatenation (which requires shifting characters) repeatedly leads to a complexity closer to , where is the input length and is the max output length.
- Why it fails: While the input constraints for
sare small (), the output length can be up to . Repeatedly reconstructing the entire string for every nested layer creates unnecessary overhead. Furthermore, implementing the parsing logic to correctly identifykand handle string splicing manually is error-prone during an interview.
Algorithm Strategy: Stack Patterns
We will use two stacks (or a single stack storing objects) to simulate the decoding process: one for counts (countStack) and one for strings (stringStack). We also maintain two variables: currentString (to build the current segment) and k (to build the current repeat number).
- Iterate through the input string character by character.
- Case 1: Digit. If the character is a digit, update
k. Since numbers can have multiple digits (e.g.,12), we calculatek = k * 10 + digit. - Case 2: Opening Bracket
[. This implies the start of a new encoded section.- Push the
currentStringontostringStack. - Push the calculated
kontocountStack. - Reset
currentStringto empty andkto 0.
- Push the
- Case 3: Closing Bracket
]. This implies the end of the current encoded section.- Pop the repeat count
currentKfromcountStack. - Pop the
previousStringfromstringStack. - Update
currentStringby decoding:currentString = previousString + (currentString repeated currentK times).
- Pop the repeat count
- Case 4: Letter. Simply append the character to
currentString. - Return
currentStringafter the loop finishes.
This strategy ensures that currentString always holds the fully decoded content of the current nesting level.
Execution Flow
Let's trace the algorithm with input: 3[a2[c]]
- Initialize:
countStack = [],stringStack = [],currentString = "",k = 0. - Process '3':
- It's a digit.
kbecomes 3.
- It's a digit.
- Process '[':
- Push
currentString("") tostringStack. - Push
k(3) tocountStack. - Reset
currentString = "",k = 0. - Stacks:
stringStack=[""],countStack=[3]
- Push
- Process 'a':
- It's a letter.
currentStringbecomes "a".
- It's a letter.
- Process '2':
- It's a digit.
kbecomes 2.
- It's a digit.
- Process '[':
- Push
currentString("a") tostringStack. - Push
k(2) tocountStack. - Reset
currentString = "",k = 0. - Stacks:
stringStack=["", "a"],countStack=[3, 2]
- Push
- Process 'c':
- It's a letter.
currentStringbecomes "c".
- It's a letter.
- Process ']':
- Pop
currentK(2) fromcountStack. - Pop
prevString("a") fromstringStack. currentStringbecomesprevString+ (currentString*currentK)currentString= "a" + ("c" * 2) = "acc".- Stacks:
stringStack=[""],countStack=[3]
- Pop
- Process ']':
- Pop
currentK(3) fromcountStack. - Pop
prevString("") fromstringStack. currentStringbecomes "" + ("acc" * 3) = "accaccacc".- Stacks: Empty.
- Pop
- End: Return "accaccacc".
Proof of Correctness
The correctness of this algorithm relies on the stack invariant: the stack always stores the valid state of the outer context while the inner context is being processed.
When we encounter a ], the currentString variable holds the correct decoded value for the innermost expression. The top of stringStack holds the prefix that appeared before this expression, and the top of countStack holds the multiplier for this expression. By combining pop(stringStack) + currentString * pop(countStack), we correctly reconstruct the string at that specific nesting depth. By induction, since the base case (no brackets) is handled correctly by simple appending, and each reduction step correctly merges an inner scope into an outer scope, the final result is correct.
Pattern Reuse Notes
The Stack - Simulation / Backtracking Helper pattern is highly versatile. Understanding how to use a stack to maintain "parent" state while processing "child" state is crucial for several other problems:
- LeetCode 71: Simplify Path: Uses a stack to process directory components, pushing valid names and popping on
..to simulate directory navigation. - LeetCode 735: Asteroid Collision: Uses a stack to simulate the state of stable asteroids. Collisions (nested interactions) are resolved by popping from the stack until a stable state is reached.
Feynman coach · Before you peek at the code
Can you explain the core insight in your own words?
If you can explain it before reading code, you’re far more likely to recall it under interview pressure.
Reference Implementation
C++ Solution for LeetCode 394
1#include <iostream>
2#include <string>
3#include <stack>
4#include <cctype>
5
6class Solution {
7public:
8 string decodeString(string s) {
9 std::stack<int> countStack;
10 std::stack<std::string> stringStack;
11 std::string currentString = "";
12 int k = 0;
13
14 for (char ch : s) {
15 if (isdigit(ch)) {
16 // Build the number k (handles multi-digit numbers)
17 k = k * 10 + (ch - '0');
18 } else if (ch == '[') {
19 // Push current context to stacks
20 countStack.push(k);
21 stringStack.push(currentString);
22 // Reset context for the inner bracket
23 currentString = "";
24 k = 0;
25 } else if (ch == ']') {
26 // Decode current level
27 std::string decodedString = stringStack.top();
28 stringStack.pop();
29
30 int currentK = countStack.top();
31 countStack.pop();
32
33 // Append the current string k times
34 for (int i = 0; i < currentK; i++) {
35 decodedString += currentString;
36 }
37 currentString = decodedString;
38 } else {
39 // Regular character
40 currentString += ch;
41 }
42 }
43
44 return currentString;
45 }
46};Java Solution for LeetCode 394
1import java.util.Stack;
2
3class Solution {
4 public String decodeString(String s) {
5 Stack<Integer> countStack = new Stack<>();
6 Stack<StringBuilder> stringStack = new Stack<>();
7 StringBuilder currentString = new StringBuilder();
8 int k = 0;
9
10 for (char ch : s.toCharArray()) {
11 if (Character.isDigit(ch)) {
12 k = k * 10 + (ch - '0');
13 } else if (ch == '[') {
14 countStack.push(k);
15 stringStack.push(currentString);
16 currentString = new StringBuilder();
17 k = 0;
18 } else if (ch == ']') {
19 StringBuilder decodedString = stringStack.pop();
20 int currentK = countStack.pop();
21
22 // Append currentString repeated currentK times to the popped string
23 for (int i = 0; i < currentK; i++) {
24 decodedString.append(currentString);
25 }
26 currentString = decodedString;
27 } else {
28 currentString.append(ch);
29 }
30 }
31
32 return currentString.toString();
33 }
34}Python Solution for LeetCode 394
1class Solution:
2 def decodeString(self, s: str) -> str:
3 stack = []
4 current_string = ""
5 k = 0
6
7 for char in s:
8 if char.isdigit():
9 k = k * 10 + int(char)
10 elif char == "[":
11 # Push the tuple (current_string, k) onto the stack
12 stack.append((current_string, k))
13 # Reset for the new context
14 current_string = ""
15 k = 0
16 elif char == "]":
17 # Pop the last context
18 prev_string, prev_k = stack.pop()
19 current_string = prev_string + (current_string * prev_k)
20 else:
21 current_string += char
22
23 return current_string