Start learning free

LESSON 397 OF 540 · STACK

Evaluate Reverse Polish Notation in Kotlin: O(n) with a Stack

Practice O(n) Read the full explanation

In reverse Polish notation, also called postfix notation, an operator comes after its two operands: 3 + 4 is written 3 4 +, and (1 + 2) * 3 is written 1 2 + 3 *. It needs no parentheses: the order of the tokens alone says what is computed first.

Given an array of strings tokens that holds a valid expression in reverse Polish notation, return the value of the expression.

  • Each token is either an operator, "+", "-", "*" or "/", or an integer such as "42" or "-7".
  • An operator works on the two values that come right before it, once those are worked out: the first of them is its left operand and the second its right operand. 8 3 - is 8 - 3 = 5, not 3 - 8.
  • The division of two integers truncates toward zero: it drops the fraction, so 7 / 2 is 3 and -7 / 2 is -3 (not -4).

 

Example 1:

Input: tokens = ["2", "1", "+", "3", "*"]
Output: 9
Explanation: ((2 + 1) * 3) = 9

Example 2:

Input: tokens = ["4", "13", "5", "/", "+"]
Output: 6
Explanation: (4 + (13 / 5)) = 4 + 2 = 6, because 13 / 5 is 2.6, truncated to 2.

Example 3:

Input: tokens = ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"]
Output: 22
Explanation:
  ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 22
6 / -132 is about -0.045, and truncating it toward zero gives 0.

 

Constraints:

  • 1 <= tokens.length <= 105
  • tokens[i] is "+", "-", "*", "/", or an integer.
  • Every integer in tokens, and every value computed along the way, fits in a 32-bit signed integer: from -231 to 231 - 1.
  • tokens is a valid expression in reverse Polish notation, and no division is by zero.

Note:

Your algorithm should run in O(n) time and use O(n) extra space, where n is the number of tokens.

STEP 1 OF 11
    Ctrl + Enter to check

    KOTLIN · STACK

    Evaluate Reverse Polish Notation in Kotlin, explained

    Problem overview

    In reverse Polish notation, also called postfix notation, an operator comes after its two operands. 3 + 4 is written 3 4 +, and (1 + 2) * 3 is written 1 2 + 3 *. No parentheses are needed, because the order of the tokens says what to compute first.

    The function evalRPN(tokens: Array<String>): Int receives a valid expression as an array of strings and returns its value. Each token is one of the operators "+", "-", "*", "/", or an integer such as "42" or "-7". An operator works on the two values right before it, once they are worked out: the first is its left operand, the second its right one, so 8 3 - is 8 - 3 = 5. Division truncates toward zero, so -7 / 2 is -3.

    For ["2", "1", "+", "3", "*"] the result is 9, since (2 + 1) * 3 = 9. For ["4", "13", "5", "/", "+"] it is 6, since 13 / 5 is 2 after truncation and 4 + 2 = 6.

    The lesson asks for O(n) time and O(n) extra space, where n is the number of tokens.

    Approach: a stack of values worked out so far

    Read the tokens from left to right. A number can't be used until an operator asks for it, so it waits. When an operator arrives, its operands are the two most recent values that haven't been used yet. That is exactly what the top of a stack holds: the last value pushed is the first one popped.

    So the rule has two cases:

    • A number: push it.
    • An operator: pop two values, apply the operator, and push the result. The result takes the place of its two operands, and a later operator may use it as one of its own.

    Every operator turns two values into one, so a valid expression ends with exactly one value on the stack: the answer. Each token is handled once with constant work, which gives O(n) time, and the stack never holds more than n values.

    The order of the pops matters. The right operand was computed last, so it is on top and comes off first. Pop it into b, then pop a, and compute a - b or a / b. Swapping them would turn 8 3 - into 3 - 8.

    Here is the stack for ["4", "13", "5", "/", "+"]:

    token 4: stack=[4]
    token 13: stack=[4, 13]
    token 5: stack=[4, 13, 5]
    token /: a=13 b=5 stack=[4, 2]
    token +: a=4 b=2 stack=[6]
    

    Step by step

    1. Create the stack: a val named stack, an empty ArrayDeque<Int>(). addLast and removeLast both work at the end, so the end is the top.
    2. Loop over the tokens, from left to right, one at a time.
    3. Test for an operator by comparing the token with each of the four operator strings, joined with ||. A negative number such as "-7" starts with a minus sign, but it isn't equal to the one-character string "-", so it isn't mistaken for the operator.
    4. Pop the right operand with removeLast() into b.
    5. Pop the left operand into a.
    6. For "+", push a + b.
    7. For "-", push a - b, where the order matters.
    8. For "*", push a * b.
    9. Otherwise it is "/", so push a / b. Kotlin's integer division already truncates toward zero.
    10. For a number, push token.toInt(). It sits on the stack until an operator uses it.
    11. Return the last value, taken with removeLast().

    The solution in Kotlin

    class Solution {
        fun evalRPN(tokens: Array<String>): Int {
            val stack = ArrayDeque<Int>()
            for (token in tokens) {
                if (token == "+" || token == "-" || token == "*" || token == "/") {
                    val b = stack.removeLast()
                    val a = stack.removeLast()
                    if (token == "+") {
                        stack.addLast(a + b)
                    } else if (token == "-") {
                        stack.addLast(a - b)
                    } else if (token == "*") {
                        stack.addLast(a * b)
                    } else {
                        stack.addLast(a / b)
                    }
                } else {
                    stack.addLast(token.toInt())
                }
            }
            return stack.removeLast()
        }
    }
    

    Kotlin details worth a look:

    • ArrayDeque as a stack. It is Kotlin's own kotlin.collections.ArrayDeque, available without an import. addLast pushes and removeLast pops, both in constant time. removeLast on an empty deque throws NoSuchElementException, which can't happen for a valid expression.
    • String comparison. == in Kotlin compares contents (it calls equals), so token == "+" is the right test. "-" == "-7" is false, which is why negative numbers go to the number branch.
    • Truncating division. On Ints, / drops the fraction toward zero: -7 / 2 is -3, and 7 / -2 is -3 too. Rounding down instead would give -4 for the first. Math.floorDiv(-7, 2) is that rounding-down version, and it isn't what this problem wants.
    • toInt(). "-7".toInt() is -7. It throws NumberFormatException for text that isn't an integer, but the tokens here are valid.
    • Int arithmetic. Results are 32-bit and overflow wraps silently. The lesson guarantees every intermediate value fits, so no wider type is needed.
    • The if chain. The last else handles "/" because the earlier tests have already ruled out the other three operators.

    Complexity

    Time: O(n). Each token is read once. Pushing, popping and the arithmetic are constant-time operations.

    Space: O(n). In the worst case, such as many numbers followed by many operators, the stack holds a large share of the tokens at once.

    Edge cases and common mistakes

    • Operand order. ["8", "3", "-"] returns 5. Computing b - a gives -5.
    • Division of negative numbers. ["-7", "2", "/"] returns -3, and ["7", "-2", "/"] returns -3. ["-7", "-2", "/"] returns 3.
    • A lone number. ["-7"] returns -7: no operator runs, and the one value on the stack is the answer.
    • Telling "-" from "-11". Checking only the first character, or using startsWith("-"), would treat negative numbers as operators. Comparing the whole token avoids that.
    • Result that truncates to zero. In the third lesson example, 6 / -132 becomes 0, and the expression finishes at 22.
    • Reading the stack at the end. After the loop, exactly one value should remain. The function removes it rather than peeking, because only one is left.

    Testing

    • ["2", "1", "+", "3", "*"] gives 9.
    • ["4", "13", "5", "/", "+"] gives 6.
    • ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"] gives 22.
    • ["8", "3", "-"] gives 5.
    • ["-7", "2", "/"] gives -3.
    • ["-7"] gives -7.
    Keep going with Kotlin500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free