Facebook Pixel

150. Evaluate Reverse Polish Notation

Problem Description

You need to evaluate an arithmetic expression given in Reverse Polish Notation (RPN) format. RPN is a mathematical notation where operators come after their operands instead of between them.

In RPN:

  • Numbers (operands) are pushed onto a stack
  • When an operator is encountered, it operates on the required number of values from the stack
  • The result is pushed back onto the stack

For example, the expression "2 1 + 3 *" means:

  1. Push 2 onto stack
  2. Push 1 onto stack
  3. See +, pop 1 and 2, calculate 2 + 1 = 3, push 3 back
  4. Push 3 onto stack
  5. See *, pop 3 and 3, calculate 3 * 3 = 9, push 9 back
  6. Return 9 (final result)

Key Rules:

  • Valid operators are +, -, *, /
  • For subtraction and division, the order matters: a - b means the second-to-last popped value minus the last popped value
  • Division truncates toward zero (e.g., 8 / -3 = -2, not -3)
  • All numbers fit in a 32-bit integer
  • The input is guaranteed to be valid (no division by zero, proper format)

The solution uses a stack-based approach:

  1. Iterate through each token in the array
  2. If it's a number, push it onto the stack
  3. If it's an operator, pop two operands, apply the operation, and push the result back
  4. The final answer is the only remaining element in the stack

The code handles the operation order carefully by popping operands in the correct sequence (s.pop(-2) for the first operand, s.pop(-1) for the second) to maintain proper subtraction and division order.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight for solving RPN expressions comes from understanding how they're naturally evaluated - they follow a stack-based evaluation model. When we read an RPN expression from left to right, we're essentially following the exact order that a computer would execute operations.

Think about how you would manually evaluate "2 1 + 3 *":

  • You'd keep track of numbers: 2, then 1
  • When you see +, you'd combine them: 2 + 1 = 3
  • You'd remember this result: 3
  • Then you see another 3
  • When you see *, you'd multiply: 3 * 3 = 9

This "keeping track" and "combining when we see an operator" behavior naturally maps to a stack data structure. Numbers get pushed (remembered), and operators trigger pops (retrieve values), computation, and a push of the result.

Why does a stack work perfectly here? Because RPN ensures that:

  1. Operators always have their operands ready - by the time we encounter an operator, its operands have already been processed and are sitting on top of the stack
  2. The most recent values are what we need - LIFO (Last In, First Out) behavior of stacks matches exactly how RPN consumes operands
  3. Intermediate results become operands - after computing a sub-expression, its result stays on the stack to potentially be used by future operations

The trickiest part is handling non-commutative operations (- and /). Since we pop from a stack, the second value popped was actually pushed first, meaning it's the left operand. So for "5 2 -", we push 5, push 2, then compute 5 - 2 (not 2 - 5). This is why the solution carefully uses s.pop(-2) as the first operand and s.pop(-1) as the second.

The algorithm essentially simulates what a stack-based calculator or processor would do when executing RPN instructions, making it both intuitive and efficient.

Solution Approach

The implementation uses a stack-based algorithm with Python's list as the stack data structure. Here's how the solution works step by step:

1. Setup Operations Dictionary:

opt = {
    "+": operator.add,
    "-": operator.sub,
    "*": operator.mul,
    "/": operator.truediv,
}

This maps operator strings to their corresponding Python functions, making the code cleaner and more maintainable.

2. Initialize Stack:

s = []

An empty list serves as our stack where we'll push operands and intermediate results.

3. Process Each Token:

for token in tokens:
    if token in opt:
        # Handle operator
        s.append(int(opt[token](s.pop(-2), s.pop(-1))))
    else:
        # Handle operand
        s.append(int(token))

For each token, we check:

  • If it's an operator: Pop two operands from the stack, apply the operation, and push the result back
  • If it's a number: Convert to integer and push onto the stack

4. Critical Detail - Order of Operations: The expression s.pop(-2), s.pop(-1) is crucial:

  • s.pop(-2) gets the second-to-last element (first operand)
  • s.pop(-1) gets the last element (second operand)

This ensures correct order for non-commutative operations. For example, with ["5", "2", "-"]:

  • Stack becomes [5, 2]
  • When we see -, we compute 5 - 2 = 3 (not 2 - 5 = -3)

5. Handle Division Truncation: The result is wrapped with int() which automatically truncates toward zero in Python, satisfying the requirement that 8 / -3 = -2.

6. Return Final Result:

return s[0]

After processing all tokens, the stack contains exactly one element - our final answer.

Time Complexity: O(n) where n is the number of tokens, as we process each token exactly once.

Space Complexity: O(n) in the worst case, when all tokens are numbers (no operations to reduce the stack size).

The beauty of this solution lies in its simplicity - it directly mirrors how RPN is mathematically defined, making the code both intuitive and efficient.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's trace through the expression ["4", "13", "5", "/", "+"] step by step to see how our solution works:

Initial Setup:

  • Stack s = []
  • Expression represents: 4 + (13 / 5) in standard notation

Step 1: Process "4"

  • Token is not an operator, so push it onto stack
  • Stack: [4]

Step 2: Process "13"

  • Token is not an operator, so push it onto stack
  • Stack: [4, 13]

Step 3: Process "5"

  • Token is not an operator, so push it onto stack
  • Stack: [4, 13, 5]

Step 4: Process "/"

  • Token is an operator, so:
    • Pop s.pop(-2) = 13 (first operand)
    • Pop s.pop(-1) = 5 (second operand)
    • Calculate: 13 / 5 = 2.6
    • Convert to int (truncate): int(2.6) = 2
    • Push result onto stack
  • Stack: [4, 2]

Step 5: Process "+"

  • Token is an operator, so:
    • Pop s.pop(-2) = 4 (first operand)
    • Pop s.pop(-1) = 2 (second operand)
    • Calculate: 4 + 2 = 6
    • Push result onto stack
  • Stack: [6]

Final Result:

  • Return s[0] = 6

This example demonstrates:

  1. How operands accumulate on the stack
  2. How operators consume exactly two operands from the stack top
  3. How intermediate results (like 13 / 5 = 2) become operands for subsequent operations
  4. How division truncation works (2.6 becomes 2)
  5. Why the final stack contains exactly one element - our answer

Solution Implementation

1from typing import List
2import operator
3
4
5class Solution:
6    def evalRPN(self, tokens: List[str]) -> int:
7        """
8        Evaluate the value of an arithmetic expression in Reverse Polish Notation.
9      
10        Args:
11            tokens: List of strings representing numbers and operators (+, -, *, /)
12          
13        Returns:
14            Integer result of the evaluated expression
15        """
16        # Define operator mapping to corresponding functions
17        operators = {
18            "+": operator.add,
19            "-": operator.sub,
20            "*": operator.mul,
21            "/": operator.truediv,
22        }
23      
24        # Initialize stack to store operands
25        stack = []
26      
27        # Process each token in the expression
28        for token in tokens:
29            if token in operators:
30                # Pop two operands from stack (order matters for non-commutative operations)
31                second_operand = stack.pop()
32                first_operand = stack.pop()
33              
34                # Apply operator and push result back to stack
35                # Use int() to truncate towards zero for division
36                result = int(operators[token](first_operand, second_operand))
37                stack.append(result)
38            else:
39                # Token is a number, push it to stack
40                stack.append(int(token))
41      
42        # Final result is the only element left in stack
43        return stack[0]
44
1class Solution {
2    /**
3     * Evaluates the value of an arithmetic expression in Reverse Polish Notation.
4     * Valid operators are +, -, *, and /.
5     * Each operand may be an integer or another expression.
6     * 
7     * @param tokens An array of strings representing the RPN expression
8     * @return The result of evaluating the expression
9     */
10    public int evalRPN(String[] tokens) {
11        // Stack to store operands
12        Deque<Integer> stack = new ArrayDeque<>();
13      
14        // Process each token in the expression
15        for (String token : tokens) {
16            // Check if token is a number (multi-digit or single digit positive number)
17            if (token.length() > 1 || Character.isDigit(token.charAt(0))) {
18                // Push the number onto the stack
19                stack.push(Integer.parseInt(token));
20            } else {
21                // Token is an operator, pop two operands
22                int secondOperand = stack.pop();  // Second operand (right)
23                int firstOperand = stack.pop();   // First operand (left)
24              
25                // Perform the operation and push result back onto stack
26                switch (token) {
27                    case "+":
28                        stack.push(firstOperand + secondOperand);
29                        break;
30                    case "-":
31                        stack.push(firstOperand - secondOperand);
32                        break;
33                    case "*":
34                        stack.push(firstOperand * secondOperand);
35                        break;
36                    case "/":
37                        stack.push(firstOperand / secondOperand);
38                        break;
39                }
40            }
41        }
42      
43        // The final result is the only element left in the stack
44        return stack.pop();
45    }
46}
47
1class Solution {
2public:
3    int evalRPN(vector<string>& tokens) {
4        // Stack to store operands during evaluation
5        stack<int> operandStack;
6      
7        // Process each token in the RPN expression
8        for (const auto& token : tokens) {
9            // Check if token is a number (multi-digit or positive single digit)
10            if (token.size() > 1 || isdigit(token[0])) {
11                // Push the number onto the stack
12                operandStack.push(stoi(token));
13            } else {
14                // Token is an operator, pop two operands from stack
15                // Note: Order matters - second operand is popped first
16                int secondOperand = operandStack.top();
17                operandStack.pop();
18                int firstOperand = operandStack.top();
19                operandStack.pop();
20              
21                // Perform the operation based on the operator
22                if (token[0] == '+') {
23                    operandStack.push(firstOperand + secondOperand);
24                } else if (token[0] == '-') {
25                    operandStack.push(firstOperand - secondOperand);
26                } else if (token[0] == '*') {
27                    operandStack.push(firstOperand * secondOperand);
28                } else {  // Division operator '/'
29                    operandStack.push(firstOperand / secondOperand);
30                }
31            }
32        }
33      
34        // The final result is the only element left in the stack
35        return operandStack.top();
36    }
37};
38
1/**
2 * Evaluates the value of an arithmetic expression in Reverse Polish Notation (RPN)
3 * @param tokens - Array of strings representing numbers and operators (+, -, *, /)
4 * @returns The result of the expression evaluation
5 */
6function evalRPN(tokens: string[]): number {
7    // Stack to store operands during evaluation
8    const operandStack: number[] = [];
9  
10    // Process each token in the expression
11    for (const token of tokens) {
12        // Check if token is a number (including negative numbers)
13        if (!isNaN(Number(token))) {
14            // Push number onto the stack
15            operandStack.push(Number(token));
16        } else {
17            // Token is an operator, pop two operands from stack
18            // Note: Order matters - second operand is popped first
19            const rightOperand: number = operandStack.pop()!;
20            const leftOperand: number = operandStack.pop()!;
21          
22            // Perform the operation based on the operator type
23            switch (token) {
24                case '+':
25                    // Addition: left + right
26                    operandStack.push(leftOperand + rightOperand);
27                    break;
28                case '-':
29                    // Subtraction: left - right
30                    operandStack.push(leftOperand - rightOperand);
31                    break;
32                case '*':
33                    // Multiplication: left * right
34                    operandStack.push(leftOperand * rightOperand);
35                    break;
36                case '/':
37                    // Division: truncate towards zero using Math.trunc
38                    operandStack.push(Math.trunc(leftOperand / rightOperand));
39                    break;
40            }
41        }
42    }
43  
44    // The final result is the only element left in the stack
45    return operandStack[0];
46}
47

Time and Space Complexity

Time Complexity: O(n) where n is the number of tokens in the input list.

  • The algorithm iterates through each token exactly once in the for loop
  • For each token, we perform constant time operations:
    • Checking if a token is an operator: O(1) (dictionary lookup)
    • Stack push operation: O(1)
    • Stack pop operation: O(1)
    • Arithmetic operation and integer conversion: O(1)
  • Therefore, the overall time complexity is O(n)

Space Complexity: O(n) where n is the number of tokens in the input list.

  • The stack s can contain at most (n + 1) / 2 elements in the worst case
    • This occurs when all operands appear first, followed by all operators
    • For a valid RPN expression with n tokens, if there are x operators, there must be x + 1 operands
    • Maximum stack size occurs just before we start processing operators: x + 1 operands on the stack
    • Since x + (x + 1) = n, we get x = (n - 1) / 2, so maximum stack size is (n + 1) / 2
  • The operator dictionary opt uses O(1) space (constant size with 4 operators)
  • Therefore, the overall space complexity is O(n)

Common Pitfalls

1. Incorrect Operand Order for Non-Commutative Operations

The most common mistake is popping operands in the wrong order when performing subtraction or division. Since these operations are not commutative, the order matters significantly.

Incorrect Implementation:

# WRONG - This reverses the operand order
second = stack.pop()
first = stack.pop()
result = operators[token](second, first)  # Bug: arguments reversed!

Why it fails: For input ["5", "2", "-"], this would calculate 2 - 5 = -3 instead of the correct 5 - 2 = 3.

Correct Implementation:

# RIGHT - Maintain proper order
second = stack.pop()  # Last element (right operand)
first = stack.pop()   # Second-to-last element (left operand)
result = operators[token](first, second)  # Correct order: first op second

2. Incorrect Division Truncation

Python's division behavior changed between Python 2 and Python 3, and the problem requires truncation toward zero, not floor division.

Incorrect Implementation:

# WRONG - Floor division doesn't truncate toward zero for negative results
"/": lambda a, b: a // b  # Bug: -7 // 3 = -3, not -2

Why it fails: Floor division rounds toward negative infinity. For -7 / 3, floor division gives -3, but truncation toward zero should give -2.

Correct Implementation:

# RIGHT - Use int() to truncate toward zero
result = int(operator.truediv(first, second))
# Or alternatively:
result = int(first / second)

3. Using One-Line Pop with Side Effects

Trying to pop both operands in a single line can lead to unexpected behavior due to evaluation order.

Incorrect Implementation:

# WRONG - Evaluation order is not guaranteed
result = operators[token](stack.pop(), stack.pop())  # Bug: order undefined!

Why it fails: Python doesn't guarantee left-to-right evaluation of function arguments. The second pop() might execute before the first, reversing your operands.

Correct Implementation:

# RIGHT - Pop operands separately with clear order
second = stack.pop()
first = stack.pop()
result = operators[token](first, second)

4. Not Handling Negative Numbers Properly

While the problem guarantees valid input, forgetting to handle negative numbers as strings can cause issues.

Potential Issue:

# This works but could be clearer
stack.append(int(token))  # Works for "-5" but not obvious

More Robust Implementation:

# Better - Explicitly handle negative numbers
if token in operators:
    # Handle operator
else:
    # Token is a number (could be negative like "-5")
    stack.append(int(token))

5. Stack Underflow Not Checked

Although the problem guarantees valid input, in real-world scenarios, you should validate that there are enough operands.

Production-Ready Enhancement:

if token in operators:
    if len(stack) < 2:
        raise ValueError(f"Insufficient operands for operator {token}")
    second = stack.pop()
    first = stack.pop()
    result = int(operators[token](first, second))
    stack.append(result)

The key to avoiding these pitfalls is understanding that RPN evaluation is inherently order-sensitive, and careful attention must be paid to maintaining the correct sequence of operations throughout the algorithm.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.

Which of the following method should we use to solve this problem?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More