Start learning free

LESSON 400 OF 543 · STACK

Evaluate Reverse Polish Notation in Rust: A Stack in O(n)

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

    RUST · STACK

    Evaluate Reverse Polish Notation in Rust, explained

    Problem overview

    In reverse Polish notation (postfix), 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 is computed first.

    eval_rpn receives tokens: Vec<String>, a valid expression, and returns its value as an i32. A 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 and the second its right operand, so 8 3 - is 8 - 3 = 5. Division truncates toward zero, so 7 / 2 is 3 and -7 / 2 is -3.

    For ["4", "13", "5", "/", "+"] the answer is 6: 13 / 5 is 2.6, truncated to 2, and 4 + 2 is 6.

    Approach: a stack of values

    Read the tokens from left to right. A number cannot be used until an operator asks for it, so keep it and wait. When an operator arrives, its operands are the two most recent values that are not used yet. That is exactly the top two items of a stack: take them off, compute, and put the result back, where it may become an operand of a later operator.

    Every operator turns two values into one, so after a valid expression the stack holds exactly one value, the answer. Each token is handled once with constant work, so the time is O(n).

    Here is the stack after each token of 4 13 5 / +:

       4 -> [4]
      13 -> [4, 13]
       5 -> [4, 13, 5]
       / -> [4, 2]
       + -> [6]

    The / removes 13 and 5 and puts back 2, then + removes 4 and 2 and puts back 6. For 2 1 + 3 * the stack goes [2], [2, 1], [3], [3, 3], [9].

    Step by step

    1. Declare the stack: let mut stack: Vec<i32> = Vec::new();. A vector's push and pop both work at its end, so the end is the top of the stack.
    2. Loop over each token in tokens, once, from left to right.
    3. Test for an operator: token == "+" || token == "-" || token == "*" || token == "/". A negative number such as "-7" starts with a minus sign, but it is not equal to the one-character token "-", so it is not mistaken for an operator. A String can be compared with a string literal using ==.
    4. Pop the right operand first: let b = stack.pop().unwrap();. It was worked out last, so it is on top. pop() returns an Option, and unwrap() takes the value out.
    5. Pop the left operand: let a = stack.pop().unwrap();. Popping b before a keeps the order the expression means: 8 3 - is a - b.
    6. Apply the operator with an if chain: "+" pushes a + b, "-" pushes a - b, "*" pushes a * b, and the last else, for "/", pushes a / b. The result takes the place of its two operands.
    7. Handle a number in the outer else: stack.push(token.parse().unwrap());. parse returns a Result, and unwrap() takes the number out. The stack's type, Vec<i32>, tells parse to produce an i32.
    8. Return the last value: stack.pop().unwrap() on its own line, with no semicolon, so it is the function's value.

    The solution in Rust

    impl Solution {
        fn eval_rpn(tokens: Vec<String>) -> i32 {
            let mut stack: Vec<i32> = Vec::new();
            for token in tokens {
                if token == "+" || token == "-" || token == "*" || token == "/" {
                    let b = stack.pop().unwrap();
                    let a = stack.pop().unwrap();
                    if token == "+" {
                        stack.push(a + b);
                    } else if token == "-" {
                        stack.push(a - b);
                    } else if token == "*" {
                        stack.push(a * b);
                    } else {
                        stack.push(a / b);
                    }
                } else {
                    stack.push(token.parse().unwrap());
                }
            }
            stack.pop().unwrap()
        }
    }
    

    The Rust details worth a closer look:

    • Integer division. Rust's / on two integers truncates toward zero, which is the rule the problem asks for: -7 / 2 is -3 and 7 / -2 is -3. Rounding down (floor division) would give -4, and div_euclid gives -4 as well, so neither fits here.
    • String against &str. token is a String and "+" is a &str. The standard library lets you compare the two with ==.
    • parse() and type inference. token.parse() could produce any number type. Rust picks i32 because the vector it is pushed onto holds i32. It accepts a leading minus sign, so "-7".parse::<i32>() is Ok(-7).
    • unwrap(). It panics on None or Err. For a valid expression, the stack always has two values when an operator arrives, and every other token is a valid integer, so it never fires. For an invalid one such as ["1", "+"] it would panic with called `Option::unwrap()` on a `None` value.
    • Ownership. for token in tokens moves the vector into the loop, so each token is an owned String. The function does not need tokens afterwards.
    • The last line. stack.pop().unwrap() has no semicolon, so its value is returned.
    • Overflow. The lesson's builds use -O, where i32 overflow wraps silently, and a debug build panics. The constraints guarantee that every value fits in an i32, so it does not come up.

    Complexity

    Time: O(n). Each of the n tokens is read once, and a push, a pop or a parse takes constant time (a number has a bounded number of digits).

    Space: O(n). In the worst case, for example when all the numbers come before all the operators, the stack holds about half of the tokens at once.

    Edge cases and common mistakes

    • Operand order. For 8 3 - the answer is 5. Using the first popped value as the left operand computes 3 - 8 and gives -5. The same applies to /.
    • Negative numbers. "-7" is a number. Checking whether a token starts with - would treat it as an operator. Compare the whole token with "-" instead.
    • Truncating division. -7 2 / gives -3, and 7 2 / gives 3. In the longer example, 6 / -132 truncates to 0, not -1.
    • A single number. ["42"] has no operator. The number is pushed and then popped as the result, so the answer is 42. ["-7"] gives -7.
    • The result goes back on the stack. Forgetting to push the result of an operator leaves too few values for the next one.
    • Using pop() without unwrap(). pop() returns an Option<i32>, which cannot be added to a number. The program does not compile until the value is taken out.

    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", "2", "/"] gives 3 and ["7", "-2", "/"] gives -3.
    • ["42"] gives 42.
    Keep going with Rust500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free