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:
- Push 2 onto stack
- Push 1 onto stack
- See
+
, pop 1 and 2, calculate2 + 1 = 3
, push 3 back - Push 3 onto stack
- See
*
, pop 3 and 3, calculate3 * 3 = 9
, push 9 back - 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:
- Iterate through each token in the array
- If it's a number, push it onto the stack
- If it's an operator, pop two operands, apply the operation, and push the result back
- 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.
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:
- 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
- The most recent values are what we need - LIFO (Last In, First Out) behavior of stacks matches exactly how RPN consumes operands
- 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 compute5 - 2 = 3
(not2 - 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 EvaluatorExample 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
- Pop
- 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
- Pop
- Stack:
[6]
Final Result:
- Return
s[0] = 6
This example demonstrates:
- How operands accumulate on the stack
- How operators consume exactly two operands from the stack top
- How intermediate results (like
13 / 5 = 2
) become operands for subsequent operations - How division truncation works (
2.6
becomes2
) - 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)
- Checking if a token is an operator:
- 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 arex
operators, there must bex + 1
operands - Maximum stack size occurs just before we start processing operators:
x + 1
operands on the stack - Since
x + (x + 1) = n
, we getx = (n - 1) / 2
, so maximum stack size is(n + 1) / 2
- The operator dictionary
opt
usesO(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.
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
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!