Lesson 51 of 164
Evaluate a Postfix Expression
In postfix notation (also called Reverse Polish Notation), an operator comes after its two operands: 2 3 + means 2 + 3. No parentheses are needed, because the order of the tokens fully determines the order of evaluation.
Given a postfix expression whose tokens are separated by spaces, evaluate it. The operators are +, -, *, and /, and division truncates toward zero. Return OptionalInt.empty() if the expression is invalid.
LeetCode: 150. Evaluate Reverse Polish Notation
Example 1
Input: "2 1 + 3 *"
Output: 9
Explanation: (2 + 1) * 3Example 2
Input: "4 13 5 / +"
Output: 6
Explanation: 4 + (13 / 5) = 4 + 2Example 3
Input: "1 +"
Output: empty (+ has only one operand)A helper that applies an operator
OptionalInt is a container that holds either one int or nothing. OptionalInt.of(5) holds a value and OptionalInt.empty() holds nothing, so a method can report "no answer" without returning null.
import java.util.OptionalInt;
public class Main {
static OptionalInt apply(String op, int a, int b) {
switch (op) {
case "+": return OptionalInt.of(a + b);
case "-": return OptionalInt.of(a - b);
case "*": return OptionalInt.of(a * b);
case "/": return b == 0 ? OptionalInt.empty() : OptionalInt.of(a / b); // empty on division by zero
default: return OptionalInt.empty(); // unknown operator
}
}
public static void main(String[] args) {
System.out.println(apply("-", 7, 2));
System.out.println(apply("/", 7, 0));
}
}OptionalInt[5]
OptionalInt.emptya is the left operand and b the right one. For - and / the order matters: "7 2 -" is 7 - 2.
Brute force: rewrite the token list
Find the first operator. The two tokens right before it are its operands. Replace those three tokens with the result, and repeat until one number is left.
[2, 1, +, 3, *] -> [3, 3, *] -> [9]import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.OptionalInt;
public class Main {
static OptionalInt apply(String op, int a, int b) {
switch (op) {
case "+": return OptionalInt.of(a + b);
case "-": return OptionalInt.of(a - b);
case "*": return OptionalInt.of(a * b);
case "/": return b == 0 ? OptionalInt.empty() : OptionalInt.of(a / b);
default: return OptionalInt.empty();
}
}
static boolean isOperator(String t) {
return t.equals("+") || t.equals("-") || t.equals("*") || t.equals("/");
}
static OptionalInt evalPostfix(String expr) {
List<String> tokens = new ArrayList<>(Arrays.asList(expr.trim().split("\\s+")));
while (tokens.size() > 1) {
int i = 0;
while (i < tokens.size() && !isOperator(tokens.get(i))) i++;
if (i < 2 || i == tokens.size()) {
return OptionalInt.empty(); // operator without two operands, or no operator left
}
int a, b;
try {
a = Integer.parseInt(tokens.get(i - 2));
b = Integer.parseInt(tokens.get(i - 1));
} catch (NumberFormatException e) {
return OptionalInt.empty();
}
OptionalInt value = apply(tokens.get(i), a, b);
if (value.isEmpty()) return OptionalInt.empty();
tokens.subList(i - 2, i + 1).clear(); // remove the three tokens
tokens.add(i - 2, String.valueOf(value.getAsInt())); // insert the result
}
try {
return OptionalInt.of(Integer.parseInt(tokens.get(0)));
} catch (NumberFormatException e) {
return OptionalInt.empty();
}
}
public static void main(String[] args) {
System.out.println(evalPostfix("2 1 + 3 *"));
System.out.println(evalPostfix("4 13 5 / +"));
System.out.println(evalPostfix("1 +"));
}
}OptionalInt[9]
OptionalInt[6]
OptionalInt.emptyTime: O(n²): each of up to n/2 rewrites searches and shifts the token list. Space: O(n).
Optimal: a stack of operands
Scan the tokens from left to right:
- Number: push it.
- Operator: pop the right operand, then the left operand, apply the operator, and push the result.
At the end, the stack must hold exactly one value: the answer.
Walkthrough with "2 1 + 3 *":
| token | action | stack after (bottom → top) |
|---|---|---|
| 2 | push | [2] |
| 1 | push | [2, 1] |
| + | pop 1 and 2, push 2 + 1 | [3] |
| 3 | push | [3, 3] |
| * | pop 3 and 3, push 3 * 3 | [9] |
ArrayDeque is the standard stack in Java: push and pop work at the front in O(1). The legacy Stack class is synchronized and slower, so it is not used here.
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.OptionalInt;
public class Main {
static OptionalInt apply(String op, int a, int b) {
switch (op) {
case "+": return OptionalInt.of(a + b);
case "-": return OptionalInt.of(a - b);
case "*": return OptionalInt.of(a * b);
case "/": return b == 0 ? OptionalInt.empty() : OptionalInt.of(a / b);
default: return OptionalInt.empty();
}
}
static OptionalInt evalPostfix(String expr) {
Deque<Integer> stack = new ArrayDeque<>();
for (String token : expr.trim().split("\\s+")) {
try {
stack.push(Integer.parseInt(token));
} catch (NumberFormatException notANumber) {
if (stack.size() < 2) return OptionalInt.empty(); // operator without two operands
int b = stack.pop(); // right operand is on top
int a = stack.pop();
OptionalInt result = apply(token, a, b);
if (result.isEmpty()) return OptionalInt.empty();
stack.push(result.getAsInt());
}
}
return stack.size() == 1 ? OptionalInt.of(stack.pop()) : OptionalInt.empty();
}
public static void main(String[] args) {
System.out.println(evalPostfix("2 1 + 3 *"));
System.out.println(evalPostfix("4 13 5 / +"));
System.out.println(evalPostfix("10 6 9 3 + -11 * / * 17 + 5 +"));
System.out.println(evalPostfix("1 +"));
System.out.println(evalPostfix("1 2"));
System.out.println(evalPostfix("4 0 /"));
}
}OptionalInt[9]
OptionalInt[6]
OptionalInt[22]
OptionalInt.empty
OptionalInt.empty
OptionalInt.emptyTime: O(n): one pass, O(1) per token. Space: O(n) for the stack.
Each check handles one kind of invalid input:
stack.size() < 2: an operator without enough operands ("1 +").result.isEmpty(): an unknown operator, or division by zero ("4 0 /").- the final
size() == 1check: leftover operands ("1 2").
Parsing each token as a number first means -11 is treated as a negative number, not as the - operator.
Edge cases
- A single number:
"5"evaluates to5. - Negative results and truncation:
"7 -2 /"is-3, since Java's/oninttruncates toward zero. - Overflow: Java
intarithmetic wraps around silently on overflow. For large inputs, uselongandMath.addExact/Math.multiplyExact, which throwArithmeticExceptioninstead.
