Start learning free

LESSON 325 OF 414 · STACK

Decode String in Scala: A Stack of Counts and Buffers

Practice O(n) Read the full explanation

Given an encoded string, return its decoded string.

The encoding rule is: k[encoded_string], where the encoded_string inside the square brackets is being repeated exactly k times. Note that k is guaranteed to be a positive integer.

You may assume that the input string is always valid; there are no extra white spaces, square brackets are well-formed, etc. Furthermore, you may assume that the original data does not contain any digits and that digits are only for those repeat numbers, k. For example, there will not be input like 3a or 2[4].

The test cases are generated so that the length of the output will never exceed 105.


Example 1:

Input: s = "3[a]2[bc]"
Output: "aaabcbc"

Example 2:

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

Example 3:

Input: s = "2[abc]3[cd]ef"
Output: "abcabccdcdcdef"

Constraints:

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

STEP 1 OF 16
    Ctrl + Enter to check

    SCALA · STACK

    Decode String in Scala, explained

    Problem overview

    The method decodeString receives an encoded string s and returns the decoded one. The rule is k[encoded_string]: the text inside the square brackets is repeated exactly k times. Groups can be nested, and plain letters may appear between and around them. The input is always valid, and digits appear only as repeat counts.

    For "3[a]2[bc]" the result is "aaabcbc": "a" three times, then "bc" twice. For "3[a2[c]]" the inner group gives "cc", the outer text becomes "acc", and three copies of it make "accaccacc". For "2[abc]3[cd]ef" the result is "abcabccdcdcdef".

    Approach: finish the inside before the outside

    A group can't be expanded until its closing bracket arrives, because everything inside it, nested groups included, must be decoded first. This is a job for a stack: inner work is on top, and finishing it hands a result down to the enclosing level.

    The solution keeps one stack of strings, and each entry is either a count or a piece of text:

    • A [ pushes an empty string, a new buffer for the text inside the bracket.
    • A letter is appended to the top buffer.
    • A digit builds a count. The first digit pushes a new entry, and further digits are appended to that same entry, so 23 is one entry and not two.
    • A ] pops the finished text, pops the count beneath it, repeats the text and appends the result to whatever is now on top.

    Every [ is preceded by its count, so when a ] arrives the stack ends with count, text. Here is the stack after each character of "3[a2[c]]":

    3  ["3"]
    [  ["3", ""]
    a  ["3", "a"]
    2  ["3", "a", "2"]
    [  ["3", "a", "2", ""]
    c  ["3", "a", "2", "c"]
    ]  ["3", "acc"]
    ]  ["accaccacc"]
    

    The first ] pops "c" and "2" and appends "cc" to "a". The second pops "acc" and "3". The stack is then empty, so the repeated text becomes the only entry.

    Step by step

    1. Create the stack as a val bound to an empty ArrayBuffer[String]. The last element is the top.
    2. Loop over the characters with for (c <- s).
    3. Handle [. Push an empty string with stack += "".
    4. Handle letters with c.isLetter. If the stack is empty, push an empty string first, so letters before any bracket have a buffer. Then append c to the last entry.
    5. Handle digits with c.isDigit. Read the top entry into top, or "" when the stack is empty.
    6. Start or continue a count. If top is not made only of digits, which top.matches("\\d+") tests, a new count begins, so push c.toString. Otherwise append the digit to the last entry.
    7. Handle ] in the final else. Remove the last entry into currStr, then remove the next one and convert it with toInt into currNum.
    8. Repeat the text with currStr * currNum.
    9. Put the result back. If the stack is now empty, push the repeated text. Otherwise append it to the last entry, which folds it into the enclosing level.
    10. Return the last entry of the stack. For valid input exactly one entry is left: the whole decoded string.

    The solution in Scala

    class Solution {
        def decodeString(s: String): String = {
            val stack = scala.collection.mutable.ArrayBuffer[String]()
            for (c <- s) {
                if (c == '[') {
                    stack += ""
                } else if (c.isLetter) {
                    if (stack.isEmpty) {
                        stack += ""
                    }
                    stack(stack.length - 1) += c
                } else if (c.isDigit) {
                    val top = if (stack.isEmpty) "" else stack(stack.length - 1)
                    if (!top.matches("\\d+")) {
                        stack += c.toString
                    } else {
                        stack(stack.length - 1) += c
                    }
                } else {
                    val currStr = stack.remove(stack.length - 1)
                    val currNum = stack.remove(stack.length - 1).toInt
                    val repeated = currStr * currNum
                    if (stack.isEmpty) {
                        stack += repeated
                    } else {
                        stack(stack.length - 1) += repeated
                    }
                }
            }
            stack(stack.length - 1)
        }
    }
    

    Scala details:

    • stack(i) += c works on a buffer element. It is shorthand for stack(i) = stack(i) + c: a string plus a Char gives a new string, which is stored back into the slot.
    • Multiplying a string. currStr * currNum repeats the string currNum times. It comes from Scala's string extension methods, and "ab" * 3 is "ababab". A count of 0 gives the empty string.
    • matches tests the whole string. "12".matches("\\d+") is true, while "" and "a1" give false. The empty string fails because + demands at least one digit, which is what makes the stand-in "" for an empty stack safe. In an ordinary string literal the regular expression needs the doubled backslash.
    • if is an expression. val top = if (stack.isEmpty) "" else stack(stack.length - 1) is Scala's replacement for a ternary operator.
    • remove(index) returns the element it removes, so a single call reads and pops.
    • Last expression. The method ends with stack(stack.length - 1), which is its result without a return.

    Complexity

    Time: proportional to the length of the decoded output plus the cost of copying strings. Each ] builds the repeated string once, and strings are copied again as groups are folded into their parents. Nested groups can copy an inner result once per level, so the work is bounded by the output length times the nesting depth, which stays small for the limits here.

    Space: O(L), where L is the length of the decoded output, since the final string is built in the stack. The number of entries is at most about twice the nesting depth, plus one.

    Edge cases and common mistakes

    • Multi-digit counts. "10[a]" returns "aaaaaaaaaa". The digit 0 is appended to the entry "1" because that entry is all digits.
    • Letters around groups. "2[a]bc" returns "aabc", and "ab2[c]d" returns "abccd". A digit after text starts a new count entry, because the top is text and not digits.
    • Plain text. "abc" returns "abc": the first letter pushes the buffer.
    • Deep nesting. "2[2[2[a]]]" returns "aaaaaaaa".
    • Empty group. "12[]" returns the empty string.
    • Popping in the wrong order. The text is on top and the count is beneath it. Reading the count first would try to convert the text to a number.
    • Treating a digit as a count when it continues the previous one. Pushing every digit as its own entry turns "12[a]" into a stack that has no single count to pop.
    • Parsing a count with c.toInt. On a Char, toInt gives its character code, not its digit value: '3'.toInt is 51. The solution avoids this by building the count as a string and converting the whole string.

    Testing

    • "3[a]2[bc]" returns "aaabcbc".
    • "3[a2[c]]" returns "accaccacc".
    • "2[abc]3[cd]ef" returns "abcabccdcdcdef".
    • "ab2[c]d" returns "abccd".
    Keep going with ScalaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free