Start learning free

LESSON 393 OF 536 · STACK

Evaluate Reverse Polish Notation in JavaScript: 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

    JAVASCRIPT · STACK

    Evaluate Reverse Polish Notation in JavaScript, 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: the order of the tokens alone says what is computed first.

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

    For ["2", "1", "+", "3", "*"] the answer is 9, which is (2 + 1) * 3. For ["4", "13", "5", "/", "+"] it is 6: 13 / 5 is 2.6, truncated to 2, and 4 + 2 is 6. The statement asks for O(n) time and O(n) extra space, where n is the number of tokens.

    Approach: a stack of values

    Read the tokens from left to right. A number can't be used until an operator arrives, 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 a stack gives: the most recently added value is on top. The operator takes the two top values, computes, and pushes the result, which takes the place of its two operands so that a later operator can use it.

    In JavaScript an array works as a stack: push adds to the end and pop removes from the end, so the end of the array is the top of the stack.

    Every operator turns two values into one, and every number adds one value. In a valid expression, exactly one value is left at the end, and that value is the result. Each token is handled once, with a constant amount of work, so the whole evaluation is linear.

    The order of the two pops matters. The right operand was computed last, so it is on top: pop it first as b, then pop the left operand as a. The operation is then a - b or a / b, as written.

    Step by step

    1. Create an empty array stack. It holds the values worked out so far, the most recent on top.
    2. Loop over the tokens with for...of. Each token is read once.
    3. Test for an operator by comparing 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 token "-", so it isn't mistaken for the operator.
    4. Pop the right operand into b.
    5. Pop the left operand into a.
    6. For "+", push a + b.
    7. For "-", push a - b. The order matters here.
    8. For "*", push a * b.
    9. For "/", push Math.trunc(a / b). JavaScript's / keeps the fraction, so -7 / 2 is -3.5, and Math.trunc drops the fraction to give -3.
    10. For anything else, a number, push parseInt(token). The value waits on the stack until an operator uses it.
    11. After the loop, pop the one value left and return it.

    The solution in JavaScript

    class Solution {
        evalRPN(tokens) {
            let stack = [];
            for (let token of tokens) {
                if (token === "+" || token === "-" || token === "*" || token === "/") {
                    let b = stack.pop();
                    let a = stack.pop();
                    if (token === "+") {
                        stack.push(a + b);
                    } else if (token === "-") {
                        stack.push(a - b);
                    } else if (token === "*") {
                        stack.push(a * b);
                    } else {
                        stack.push(Math.trunc(a / b));
                    }
                } else {
                    stack.push(parseInt(token));
                }
            }
            return stack.pop();
        }
    }
    

    The JavaScript details worth a closer look:

    • Tokens are strings, so convert them. The input holds "2", not 2. parseInt("-7") gives the number -7. Without the conversion, + would join strings: "-7" + "2" is "-72", not -5.
    • Math.trunc against Math.floor. Math.trunc(-7 / 2) is -3, as the problem asks. Math.floor(-7 / 2) is -4, which rounds toward negative infinity and is wrong for negative results.
    • Negative zero. Math.trunc of a small negative fraction gives -0: Math.trunc(6 / -132) is -0. JavaScript has a negative zero, and -0 === 0 is true. Adding to it, as the third example does with 10 * -0 + 17, gives an ordinary number. If the final answer is -0, as for ["-1", "2", "/"], console.log in Node prints -0 although the value equals 0.
    • === compares strings exactly. The operator test uses ===, so a token is an operator only when it is exactly that one character.
    • pop() on an empty array. It returns undefined instead of throwing. A valid expression never pops from an empty stack, but an invalid one would silently compute with undefined and end up with NaN.
    • Safe size. The values and every result along the way fit in a 32-bit signed integer, and JavaScript numbers hold such integers exactly.

    Tracing ["2", "1", "+", "3", "*"]

    tokenactionstack afterwards
    "2"push 2[2]
    "1"push 1[2, 1]
    "+"pop b = 1, a = 2, push 3[3]
    "3"push 3[3, 3]
    "*"pop b = 3, a = 3, push 9[9]

    The loop ends, the last pop() returns 9.

    Tracing ["4", "13", "5", "/", "+"]

    tokenactionstack afterwards
    "4"push 4[4]
    "13"push 13[4, 13]
    "5"push 5[4, 13, 5]
    "/"pop b = 5, a = 13, push 2[4, 2]
    "+"pop b = 2, a = 4, push 6[6]

    Complexity

    Time: O(n). Each token is read once. A number costs one push, and an operator costs two pops and one push, all constant-time array operations at the end of the array.

    Space: O(n). In the worst case all the numbers come first and the stack holds about half of the tokens before any operator runs.

    Edge cases and common mistakes

    • A single number. ["42"] returns 42: the number is pushed, and the final pop() returns it.
    • Operand order. ["8", "3", "-"] returns 5, and ["3", "8", "-"] returns -5. Popping a before b would swap the operands and reverse every subtraction and division.
    • Truncating division. ["7", "2", "/"] returns 3, ["-7", "2", "/"] returns -3, and ["7", "-2", "/"] returns -3. Using Math.floor would give -4 for the last two.
    • Division below one. ["1", "2", "/"] returns 0.
    • Negative numbers read as operators. Checking token.startsWith("-") or token[0] === "-" would treat "-11" as an operator. Compare the whole token instead.
    • Forgetting parseInt. "2" + "1" is "21", so addition would join strings instead of adding numbers. Subtraction and multiplication would still convert the strings, which can hide the bug in a few test cases.
    • Returning the stack. The answer is the single number on top, not the array.

    Testing

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