Problem overview
The function decodeString receives an encoded string and returns the decoded one. The rule is k[encoded_string]: the text inside the square brackets is repeated exactly k times. Groups can nest, can sit next to plain letters, and the input is always valid. Digits appear only as repeat counts, so inputs like 3a or 2[4] never occur. A count can have several digits, up to 300, and s has at most 30 characters.
For "3[a2[c]]" the inner group 2[c] becomes cc, so the outer group is 3[acc], which is "accaccacc". For "2[abc]3[cd]ef" there are two groups and a plain tail, giving "abcabccdcdcdef".
Approach: decode the innermost group first
Nesting means the group that closes first is the one that opened last, which is the shape of a stack. The idea is to scan once from left to right and keep two kinds of entries on one stack of strings: a count, stored as a string of digits, and a text buffer for the characters collected since a [ opened.
When a ] arrives, the top entry is the finished text and the entry under it is its count. Pop both, repeat the text, and give it to whatever is now on top: if that is the buffer of an enclosing group, the decoded text is appended to it, and if the stack is empty, it becomes the first buffer of the result.
Here is "3[a2[c]]", writing each stack from bottom to top:
3 ["3"]
[ ["3", ""]
a ["3", "a"]
2 ["3", "a", "2"]
[ ["3", "a", "2", ""]
c ["3", "a", "2", "c"]
] ["3", "acc"]
] ["accaccacc"]
Each ] removes the two top entries and puts back one, so the stack shrinks by one entry per group. At the end a single entry is left: the decoded string.
Step by step
- Create the stack as an empty mutable list of strings. Counts and text share it, so every entry is a string.
- Loop over the characters of
sasc. Each character is a bracket, a letter or a digit, and each kind is handled separately. - Handle
[. Push an empty string. It will collect the text of this group. - Handle letters. A letter belongs to the text, never to a count.
- Make sure there is a buffer. A letter can appear before any bracket, as in the
eftail of the third example, when the stack may be empty. In that case push an empty buffer first, then append the letter to the last entry. - Handle digits. They build a count, which must stay apart from the decoded text.
- Peek at the top. Read the last entry, or an empty string when the stack is empty, into
top. - Start a new count when
topisn't made only of digits: push the digit as its own entry. A count is beginning, not continuing. - Continue the count otherwise: the top is already a number being built, so the digit is appended to it. This is how
12stays in one entry. - Handle the final case,
]. Anything that isn't[, a letter or a digit must be a closing bracket. - Pop the text into
currStr. It was pushed last, so it comes off first. - Pop the count into
currNumand convert it to anInt. - Repeat the text
currNumtimes. - Place the result. If the stack is now empty, push the repeated text as a new buffer. Otherwise append it to the last entry, which is the enclosing group's buffer.
- Return the last entry. For a valid input exactly one buffer remains and it holds the whole answer.
The solution in Kotlin
class Solution {
fun decodeString(s: String): String {
val stack = mutableListOf<String>()
for (c in s) {
if (c == '[') {
stack.add("")
} else if (c.isLetter()) {
if (stack.isEmpty()) {
stack.add("")
}
stack[stack.size - 1] += c
} else if (c.isDigit()) {
val top = if (stack.isEmpty()) "" else stack[stack.size - 1]
if (!top.matches(Regex("\\d+"))) {
stack.add(c.toString())
} else {
stack[stack.size - 1] += c
}
} else {
val currStr = stack.removeAt(stack.size - 1)
val currNum = stack.removeAt(stack.size - 1).toInt()
val repeated = currStr.repeat(currNum)
if (stack.isEmpty()) {
stack.add(repeated)
} else {
stack[stack.size - 1] += repeated
}
}
}
return stack[stack.size - 1]
}
}
The Kotlin details worth knowing:
+=on a list element. Strings are immutable, sostack[stack.size - 1] += cbuilds a new string and stores it back into that slot. It works because the list is aMutableList. The right side can be aChar, sinceString + Charappends the character.valand a mutable list.val stackstops the name from being reassigned. The list itself still grows and shrinks.- The regular expression.
"\\d+"is the pattern\d+, one or more digits, written with the backslash doubled in a Kotlin string.matchesneeds the whole string to fit the pattern, so an empty string, which is whattopholds for an empty stack, does not match. A buffer like"a"does not match either, while"12"does. isLetter()andisDigit()are methods onChar, so the character classes need no manual range checks.ifas an expression.val top = if (stack.isEmpty()) "" else stack[stack.size - 1]picks a value, as Kotlin has no ternary operator.removeAtreturns the removed element. That is why each pop can be assigned to a name in the same line.toInt()andrepeat()."12".toInt()is the number 12, and"ab".repeat(3)is"ababab".- The final
else. It runs for every character not handled earlier. With valid input that can only be].
Complexity
Let L be the length of the decoded output, which the problem keeps at or below 100,000.
Time: about O(L * d), where d is the nesting depth. Every character that ends up in the output is produced by repeat and then copied into each enclosing buffer by the concatenations on the way out. With 30 input characters the depth is small, so this is effectively linear in the output size.
Space: O(L). The stack holds the partly decoded buffers and counts, and the largest of them is no longer than the final output.
Edge cases and common mistakes
- No brackets.
"abc"pushes one buffer on the first letter and returns it unchanged. - Multi-digit counts.
"10[a]"gives tenas. Treating each digit as a separate count would break it, which is why a digit is appended to the top entry while that entry is all digits. - Letters after a group.
"ab2[c]"gives"abcc". The plainabsits below the count, and the decoded group is appended to it. - Text between groups.
"2[a]b3[c]"gives"aabccc". After the first group the stack holdsaa, andbis appended to it before the next count arrives. - Deep nesting.
"2[2[2[a]]]"gives eightas. - Using
c == "[". ACharand aStringcan't be compared with==in Kotlin; the code does not compile. Use single quotes for characters. - Forgetting to fold the group back. If the repeated text is pushed as a new entry when the stack isn't empty, the enclosing group never receives it and the final stack has extra entries.
Testing
"3[a]2[bc]"gives"aaabcbc"."3[a2[c]]"gives"accaccacc"."2[abc]3[cd]ef"gives"abcabccdcdcdef"."10[a]"gives"aaaaaaaaaa"."2[a]b3[c]"gives"aabccc".