Facebook Pixel

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 inner 2[c] to get "cc", then 3[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 numbers
  • s2 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
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 and s2 for storing accumulated strings
  • Two variables: num to build multi-digit numbers and res to build the current string segment

Algorithm:

  1. Initialize variables:

    • s1 = [] (stack for numbers)
    • s2 = [] (stack for strings)
    • num = 0 (accumulator for multi-digit numbers)
    • res = '' (current result string being built)
  2. 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 get num = 2, then num = 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) onto s1
    • Push res (the string built so far) onto s2
    • Reset both num and res 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.

  3. 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 '[': Push 3 to s1, push '' to s2, reset num = 0, res = ''
  • Read 'a': res = 'a'
  • Read '2': num = 2
  • Read '[': Push 2 to s1, push 'a' to s2, reset num = 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 Evaluator

Example 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:

  1. Read '2': It's a digit

    • num = 0 * 10 + 2 = 2
    • State: num=2, res='', s1=[], s2=[]
  2. Read '[': Opening bracket - save current state and reset

    • Push num=2 to s1s1=[2]
    • Push res='' to s2s2=['']
    • Reset: num=0, res=''
    • State: num=0, res='', s1=[2], s2=['']
  3. Read 'a': It's a letter

    • res = res + 'a' = 'a'
    • State: num=0, res='a', s1=[2], s2=['']
  4. Read '3': It's a digit

    • num = 0 * 10 + 3 = 3
    • State: num=3, res='a', s1=[2], s2=['']
  5. Read '[': Opening bracket - save current state and reset

    • Push num=3 to s1s1=[2,3]
    • Push res='a' to s2s2=['','a']
    • Reset: num=0, res=''
    • State: num=0, res='', s1=[2,3], s2=['','a']
  6. Read 'b': It's a letter

    • res = res + 'b' = 'b'
    • State: num=0, res='b', s1=[2,3], s2=['','a']
  7. 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=['']
  8. 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=[]

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() takes O(len(res) * k) time where k is the multiplier
  • String concatenation s2.pop() + res * s1.pop() takes O(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 consider m 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 most O(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 uses O(1) space
  • In the worst case with nested brackets, the stacks can hold O(n) elements, and the strings in s2 can have a combined length of O(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 = ''
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

How does merge sort divide the problem into subproblems?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More