150. Evaluate Reverse Polish Notation
Learn how to solve LeeCode 150 evaluate Reverse Polish Notation (RPN) expressions using Python. This LeetCode solution demonstrates stack-based arithmetic computation, featuring different implementation approaches and best practices for handling postfix mathematical expressions.
LeetCode Link: 150. Evaluate Reverse Polish Notation
Problem Definition
You are given an array of strings tokens that represents an arithmetic expression in a Reverse Polish Notation.
Evaluate the expression. Return an integer that represents the value of the expression.
Note that:
- The valid operators are 
'+','-','*', and'/'. - Each operand may be an integer or another expression.
 - The division between two integers always truncates toward zero.
 - There will not be any division by zero.
 - The input represents a valid arithmetic expression in a reverse polish notation.
 - The answer and all the intermediate calculations can be represented in a 32-bit integer.
 
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)) = 6
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
= 17 + 5
= 22
Constraints:
1 <= tokens.length <= 104tokens[i]is either an operator:"+","-","*", or"/", or an integer in the range[-200, 200].
A Note About Expression Notation
The expressions we're used to seeing are infix expressions, as they match our natural way of thinking. However, infix expressions aren't very computer-friendly.
Take the infix expression 4 + 13 / 5 for example. When a computer scans this from left to right, it reaches 13 and needs to:
- Check what operator follows 13
 - Compare operator precedence
 - Perform the division with 5
 - Then backtrack to position 4 to do the addition
 
Quite cumbersome, isn't it?
However, when we convert this to postfix (RPN) notation: ["4", "13", "5", "/", "+"], everything changes. The computer can process this sequentially using a stack, without worrying about operator precedence or backtracking. This makes postfix notation extremely computer-friendly.
This problem not only serves as an excellent coding exercise but also demonstrates how computers "think" differently from humans.
Thought Process
What is Reverse Polish Notation (RPN)?
RPN is a mathematical notation where the operators follow their operands (also called postfix notation). For example:
- Infix: 
(1 + 2) * (3 + 4) - RPN: 
1 2 + 3 4 + * 
Advantages of RPN:
- Eliminates the need for parentheses, making expressions unambiguous.
 - Facilitates computation using a stack: operands are pushed onto the stack, and operators pop the required operands for evaluation.
 
Approach to Solve the Problem
- Use a stack to evaluate the RPN:
- Push operands onto the stack.
 - When encountering an operator, pop the top two operands from the stack, apply the operator, and push the result back onto the stack.
 
 - Division is special:
- Integer division truncates toward zero, which differs slightly from the behavior of Python’s division operator 
/. To ensure correctness, implement a custom division function that handles truncation. 
 - Integer division truncates toward zero, which differs slightly from the behavior of Python’s division operator 
 
Solutions
Using Explicit Operations
class Solution:
    def evalRPN(self, tokens: list[str]) -> int:
        stack = []
        for token in tokens:
            if token not in ['+', '-', '*', '/']:  # Push operand onto the stack
                stack.append(int(token))
            else:  # Apply the operator to the top two operands
                op2 = stack.pop()
                op1 = stack.pop()
                if token == '+':
                    stack.append(op1 + op2)
                elif token == '-':
                    stack.append(op1 - op2)
                elif token == '*':
                    stack.append(op1 * op2)
                elif token == '/':  # Integer division truncating toward zero
                    stack.append(int(op1 / op2))
        return stack.pop()Using operator Module for Clean Code
This method uses Python's built-in operator module to map operators (+, -, *, /) to their corresponding functions. A custom div function handles integer division to ensure truncation toward zero.
from operator import add, sub, mul
def div(x, y):
    # Custom integer division with truncation toward zero
    return int(x / y) if x * y > 0 else -(abs(x) // abs(y))
class Solution:
    op_map = {'+': add, '-': sub, '*': mul, '/': div}
    
    def evalRPN(self, tokens: list[str]) -> int:
        stack = []
        for token in tokens:
            if token not in self.op_map:  # If it's an operand, push onto the stack
                stack.append(int(token))
            else:  # It's an operator, apply it to the top two operands
                op2 = stack.pop()
                op1 = stack.pop()
                stack.append(self.op_map[token](op1, op2))
        return stack.pop()
Alternative: Using eval() (Not Recommended)
Using Python’s eval() function can simplify the implementation but introduces performance and security concerns. Only use this method in controlled environments.
class Solution:
    def evalRPN(self, tokens: list[str]) -> int:
        stack = []
        for token in tokens:
            if token.lstrip('-').isdigit():  # Check if token is a number
                stack.append(int(token))
            else:
                op2 = stack.pop()
                op1 = stack.pop()
                stack.append(eval(f"{op1}{token}{op2}"))
        return stack.pop()Main reasons why eval() is not recommended:
Security: eval() can execute arbitrary Python code, which could be dangerous if the input isn't trusted
# Seemingly harmless eval:
eval("4+3")  # Works fine
# But eval can execute ANY Python code:
eval("__import__('os').system('rm -rf /')") # Could delete system files
eval("__import__('subprocess').run(['malicious_command'])") # Could run malware
eval("open('passwords.txt', 'r').read()") # Could read sensitive filesIn the above solution, while the input tokens are controlled, it's generally considered bad practice to use eval() because:
- If the input validation (
token.lstrip('-').isdigit()) fails - If the code is reused in another context where input isn't as controlled
 - If future modifications accidentally weaken the input validation
 
Performance: It's slower than direct arithmetic operations
Clarity: It makes the code less explicit about what operations are being performed
Complexity Analysis
Time Complexity: O(n)
- We iterate through each token exactly once
 - Each stack operation (push/pop) takes O(1) time
 - Each arithmetic operation takes O(1) time
 - Total time is linear with respect to the input length
 
Space Complexity: O(n)
- In the worst case, we might need to store all operands on the stack
 - For example, with input like ["1", "2", "3", "4", "+", "+", "+"]
 - Maximum stack size will be proportional to number of operands
 - Additional space used by variables is constant O(1)
 
Key Takeaways
- Stack-Based Computation: RPN expressions are naturally suited for stacks. Each operator processes the last two operands, which matches the stack's LIFO nature.
 - Integer Division: Ensure you handle division correctly with truncation toward zero, as Python’s 
/operator may not behave as expected. - RPN in Real Life: Reverse Polish Notation was popular in early calculators (like HP models) and remains an efficient way for computers to evaluate expressions.
 
Discussion