Decode String

Medium Stacks

Problem

Given an encoded string s, return its decoded string where the encoding rule is k[encoded_string], meaning the encoded_string inside the square brackets is repeated exactly k times. You may assume the input string is always valid and does not contain extra white spaces.

  • 1 ≤ s.length ≤ 30
  • s consists of lowercase English letters, digits, and square brackets '[]'.
  • s is guaranteed to be a valid encoding.
  • All integers in s are in the range [1, 300].

Example

Input: s = "3[a2[c]]"
Output: "accaccacc"

Example s = "3[a2[c]]" We scan left to right and maintain: - num: the repeat count we’re currently building (handles multi-digit like 12) - cur: the decoded text for the current bracket level, stored as a character list - stack: saved outer contexts as (prev_prefix_list, repeat_count) Start: num = 0, cur = [], stack = [] 1) Read "3" - This is a repeat count. - num = 3 2) Read "[" - We’re entering a bracketed block that will be repeated num times. - Save the current outer context, then start a fresh inner context: - stack.push(([], 3)) - Reset: cur = [], num = 0 3) Read "a" - Regular character, append to current level. - cur = ["a"] 4) Read "2" - Another repeat count for the next bracket. - num = 2 5) Read "[" - Enter a deeper nested block. Save what we have so far at this level. - stack.push((["a"], 2)) - Reset: cur = [], num = 0 6) Read "c" - Append letter. - cur = ["c"] 7) Read "]" (close inner block) - Pop the last saved context: prev = ["a"], k = 2 - Repeat the inner decoded chunk: ["c"] * 2 = ["c", "c"] - Stitch back into the previous prefix: ["a"] + ["c", "c"] = ["a", "c", "c"] - Now cur = ["a", "c", "c"] (we’re back inside the outer 3[...]) 8) Read "]" (close outer block) - Pop next context: prev = [], k = 3 - Repeat current chunk: ["a", "c", "c"] * 3 = ["a", "c", "c", "a", "c", "c", "a", "c", "c"] - Stitch back: [] + that = same list - Now cur = ["a", "c", "c", "a", "c", "c", "a", "c", "c"] End: - return ''.join(cur) -> "accaccacc"

Approach

Straightforward Solution

A naive approach might try to parse and decode the string recursively or with repeated string replacements, but this quickly becomes complex and inefficient, especially with nested patterns. Without a stack, managing nested contexts and repeat counts is error-prone.

Core Observation

The problem requires decoding nested repeated substrings, where each repeat count applies to the substring immediately following it inside brackets. The nested structure implies a last-in-first-out (LIFO) context management, naturally modeled by a stack.

Path to Optimal

Recognizing the nested, hierarchical structure of the encoding suggests using a stack to store the current prefix and repeat count before entering a new bracketed substring. Each time a '[' is encountered, the current state is pushed onto the stack, and a new context begins. When a ']' is encountered, the algorithm pops the last state, repeats the current substring accordingly, and appends it to the previous prefix. This approach cleanly handles arbitrary nesting and ensures linear time complexity.

Optimal Approach

Iterate through the string character by character. Maintain a current prefix (as a list of characters) and a current number accumulator. When a digit is found, update the number. When '[' is found, push the current prefix and number onto the stack and reset them. When ']' is found, pop the previous prefix and repeat count, repeat the current prefix accordingly, and append it to the previous prefix. For letters, append them to the current prefix. Finally, join the current prefix into the decoded string. This stack-based approach runs in O(n + m) time, where m is the decoded output length.

Time

O(n + m)

Let n be the length of the input string and m be the length of the decoded output. The algorithm scans the input once (O(n)). Each closing bracket expansion must materialize decoded characters; across all expansions the total number of characters produced is m, so the total work for expansions is O(m). Therefore overall time is O(n + m).

Space

O(m + d)

Let m be the decoded output length and d be the maximum nesting depth. The output requires O(m) space. The stack stores up to d frames (previous prefix plus repeat count). Total space is O(m + d).

Pattern Spotlight

Stack (State Preservation for Nested Structures)

When decoding nested or hierarchical structures with matching delimiters, use a stack to save and restore context at each level, enabling clean, linear-time parsing of arbitrarily nested patterns.

Solution

1class Solution:
2 def decodeString(self, s: str) -> str:
3 stack = []
4 cur = []
5 num = 0
6
7 for c in s:
8 if c.isdigit():
9 num = num * 10 + int(c)
10 elif c == '[':
11 stack.append((cur, num))
12 cur = []
13 num = 0
14 elif c == ']':
15 prev, k = stack.pop()
16 cur = prev + cur * k
17 else:
18 cur.append(c)
19
20 return ''.join(cur)

Step-by-Step Solution

1

Initialize Stack and Current State Variables for Decoding

3stack = []
4cur = []
5num = 0

Objective

To set up the data structures and variables that track the decoding state as the string is processed.

Key Insight

The stack holds tuples of (previous_prefix, repeat_count) to preserve context when entering nested brackets. The current prefix is built as a list of characters for efficient concatenation, and the current number accumulates multi-digit repeat counts. This setup enables seamless transitions between nested levels.

Interview Quick-Check

Core Logic

Using a stack to store previous prefixes and repeat counts preserves the decoding context for nested brackets.

State & Boundaries

Initialize current prefix as a list and number as zero to handle incremental building and multi-digit numbers.

Common Pitfalls & Bugs

Failing to reset the current prefix and number after pushing onto the stack leads to incorrect concatenations.

2

Iterate Through Input String to Parse Digits, Brackets, and Characters

7for c in s:
8 if c.isdigit():
9 num = num * 10 + int(c)
10 elif c == '[':
11 stack.append((cur, num))
12 cur = []
13 num = 0
14 elif c == ']':
15 prev, k = stack.pop()
16 cur = prev + cur * k
17 else:
18 cur.append(c)

Objective

To process each character, updating the current number, pushing/popping stack states, and building the decoded string accordingly.

Key Insight

Digits are accumulated to form multi-digit repeat counts. Upon encountering '[', the current prefix and number are pushed onto the stack, and the current prefix resets to start decoding the nested substring. When ']' is found, the last state is popped, and the current prefix is repeated and appended to the previous prefix. Letters are appended directly to the current prefix. This logic ensures correct decoding of nested patterns in a single pass.

Interview Quick-Check

Core Logic

The algorithm distinguishes digits, opening brackets, closing brackets, and letters to update state and stack accordingly, enabling nested decoding.

State & Boundaries

Check digit characters to build repeat counts; push state on '['; pop and repeat substring on ']'; append letters otherwise.

Common Pitfalls & Bugs

Not resetting the current number after pushing to the stack or not correctly concatenating repeated substrings leads to incorrect results.

3

Return the Fully Decoded String by Joining Current Characters

20return ''.join(cur)

Objective

To produce the final decoded string by concatenating all characters accumulated in the current prefix list.

Key Insight

The current prefix is maintained as a list of characters for efficient appends. Joining this list at the end produces the final decoded string. Overall runtime is O(n + m), where n is input length and m is decoded output length.

Interview Quick-Check

Core Logic

Joining the list of characters into a string returns the fully decoded result.

Complexity

Using a list for string building avoids costly string concatenations inside loops, ensuring linear time complexity.

Line Analysis

This solution has 4 Critical lines interviewers watch for.

Line 16 Critical
cur = prev + cur * k

Update the current prefix by concatenating the previous prefix with the current decoded substring repeated k times.

This concatenation embodies the core decoding logic, combining the repeated substring with the previously decoded prefix, enabling correct reconstruction of nested encoded patterns.

Line 9 Critical
num = num * 10 + int(c)

Update the current number by shifting previous digits and adding the new digit.

This line implements the critical logic for parsing multi-digit repeat counts, ensuring that numbers like '12' are correctly interpreted as twelve rather than separate digits.

Line 11 Critical
stack.append((cur, num))

Push the current prefix and number onto the stack to save context.

This push operation is the core mechanism that preserves the decoding context for nested brackets, allowing the algorithm to correctly resume previous states after decoding inner substrings.

Line 15 Critical
prev, k = stack.pop()

Pop the last saved prefix and repeat count from the stack.

This pop operation is essential for correctly unwinding nested decoding contexts, ensuring that repeated substrings are appended to the right previous prefix.

Line 3 Core
stack = []

Initialize an empty stack to store previous prefixes and repeat counts.

The stack preserves decoding context for nested brackets, enabling the algorithm to return to previous states after decoding inner substrings.

Line 20 Core
return ''.join(cur)

Return the fully decoded string by joining the current prefix list.

Joining the list of characters produces the final decoded string in linear time, consolidating all nested decoding results.

Line 4 Core
cur = []

Initialize current prefix as an empty list to build decoded characters.

Using a list for the current prefix allows efficient appending and concatenation, which is critical for performance in nested decoding.

Line 5 Core
num = 0

Initialize current number accumulator to zero for building multi-digit repeat counts.

This variable accumulates digits to form the repeat count before encountering a bracket, enabling correct handling of numbers with multiple digits.

Line 10 Core
elif c == '[':

Check if the current character is an opening bracket '['.

Detecting '[' signals the start of a nested substring that will be repeated, triggering a context save on the stack.

Line 12 Core
cur = []

Reset the current prefix to an empty list to start decoding the nested substring.

Resetting the current prefix prepares for building the substring inside the brackets independently from the previous context.

Line 13 Core
num = 0

Reset the current number accumulator to zero after pushing to the stack.

Resetting the number ensures that the next repeat count is parsed correctly without interference from the previous number.

Line 14 Core
elif c == ']':

Check if the current character is a closing bracket ']'.

Detecting ']' signals the end of a nested substring, triggering the restoration of the previous context and repetition of the current substring.

Line 18 Core
cur.append(c)

Append the current character to the current prefix list.

Appending letters to the current prefix builds the decoded substring incrementally, preserving order and content.

Line 8 Standard
if c.isdigit():

Check if the current character is a digit.

Identifying digits is necessary to build the repeat count for the upcoming bracketed substring.

Line 17 Standard
else:

Handle the case where the current character is a letter.

Letters are appended directly to the current prefix as part of the decoded output.

Line 7 Standard
for c in s:

Begin iterating over each character in the input string.

Processing the string character by character allows the algorithm to parse digits, brackets, and letters in a single pass.

Test Your Understanding

Why is a stack necessary to correctly decode nested encoded strings instead of using simple string concatenation or recursion without explicit state management?

Because nested encodings require preserving the state of the current prefix and repeat count before entering a deeper nested level. A stack naturally models this last-in-first-out context preservation, allowing the algorithm to restore previous states correctly after decoding inner substrings. Without a stack, managing multiple nested contexts and their repeat counts becomes error-prone and inefficient.

Related Problems

Stacks pattern

Don't just read it. Drill it.

Reconstruct Decode String from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Decode String drill

or drill a free problem