Loading problems...
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.
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:
].[.k immediately preceding the [.k[...] with the content inside the brackets repeated k times.textwhile 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 string
s are 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 identify k and handle string splicing manually is error-prone during an interview.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:
k).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]].
3, then [. We push our current state to the stack and reset.a, then 2, then [. We push the state (holding a) to the stack and reset again.c, then ]. We resolve the current level: repeat c 2 times (cc). We pop the stack to retrieve a, appending cc to it (acc).]. We resolve the level: repeat acc 3 times.![]()
Let's trace the algorithm with input: 3[a2[c]]
countStack = [], stringStack = [], currentString = "", k = 0.k becomes 3.currentString ("") to stringStack.k (3) to countStack.currentString = "", k = 0.stringStack=[""], countStack=[3]currentString becomes "a".k becomes 2.currentString ("a") to stringStack.k (2) to countStack.currentString = "", k = 0.stringStack=["", "a"], countStack=[3, 2]currentString becomes "c".currentK (2) from countStack.prevString ("a") from stringStack.currentString becomes prevString + (currentString * currentK)currentString = "a" + ("c" * 2) = "acc".stringStack=[""], countStack=[3]currentK (3) from countStack.prevString ("") from stringStack.currentString becomes "" + ("acc" * 3) = "accaccacc".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.
cpp#include <iostream> #include <string> #include <stack> #include <cctype> class Solution { public: string decodeString(string s) { std::stack<int> countStack; std::stack<std::string> stringStack; std::string currentString = ""; int k = 0; for (char ch : s) { if (isdigit(ch)) { // Build the number k (handles multi-digit numbers) k = k * 10 + (ch - '0'); } else if (ch == '[') { // Push current context to stacks countStack.push(k); stringStack.push(currentString); // Reset context for the inner bracket currentString = ""; k = 0; } else if (ch == ']') { // Decode current level std::string decodedString = stringStack.top(); stringStack.pop(); int currentK = countStack.top(); countStack.pop(); // Append the current string k times for (int i = 0; i < currentK; i++) { decodedString += currentString; } currentString = decodedString; } else { // Regular character currentString += ch; } } return currentString; } };
javaimport java.util.Stack; class Solution { public String decodeString(String s) { Stack<Integer> countStack = new Stack<>(); Stack<StringBuilder> stringStack = new Stack<>(); StringBuilder currentString = new StringBuilder(); int k = 0; for (char ch : s.toCharArray()) { if (Character.isDigit(ch)) { k = k * 10 + (ch - '0'); } else if (ch == '[') { countStack.push(k); stringStack.push(currentString); currentString = new StringBuilder(); k = 0; } else if (ch == ']') { StringBuilder decodedString = stringStack.pop(); int currentK = countStack.pop(); // Append currentString repeated currentK times to the popped string for (int i = 0; i < currentK; i++) { decodedString.append(currentString); } currentString = decodedString; } else { currentString.append(ch); } } return currentString.toString(); } }
pythonclass Solution: def decodeString(self, s: str) -> str: stack = [] current_string = "" k = 0 for char in s: if char.isdigit(): k = k * 10 + int(char) elif char == "[": # Push the tuple (current_string, k) onto the stack stack.append((current_string, k)) # Reset for the new context current_string = "" k = 0 elif char == "]": # Pop the last context prev_string, prev_k = stack.pop() current_string = prev_string + (current_string * prev_k) else: current_string += char return current_string
Time Complexity: , where is the length of the decoded output string. Although we iterate through the input string of length , the dominant operation is constructing the output string. Each character in the final output is generated and appended exactly once during the decoding steps. Note that strictly speaking, if we consider string concatenation costs, it is proportional to the size of the generated string.
Space Complexity: or .
We need space for the stacks and the result. In the worst case of deeply nested brackets, the stack depth is proportional to . However, the space required to store the currentString can grow up to (the total output size). Thus, the space complexity is dominated by the output size.
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:
.. to simulate directory navigation.While recursion is a valid way to solve this, a poorly implemented recursive solution that involves frequent string copying or concatenation (like s.substr(...) inside loops) can be slow. The stack approach typically avoids the overhead of finding matching brackets repeatedly.
Mistake: Treating digits as single characters (e.g., k = int(char)).
Why: The repeat count k can be greater than 9.
Consequence: You only capture the last digit (e.g., 0 from 10), resulting in incorrect repetition. You must accumulate digits: k = k * 10 + digit.
Mistake: When resolving ], you might be appending to the repeated incorrectly.
: The stack stores the (what came before the bracket).
: The logic must be . Reversing this order corrupts the data.
prevStringcurrentStringnewString = prevString + (repeated currentString)currentString = "" after pushing?Mistake: Forgetting to reset currentString or k after pushing to the stack.
Why: Once you enter a bracket [, you are starting a fresh context. The old context is saved on the stack.
Consequence: If you don't reset, the inner bracket processing will inherit "stale" data from the outer bracket, leading to duplicated or malformed strings.