A detailed explanation of evaluating arithmetic expressions with stack-based parsing and operator precedence.
Problem Restatement
We are given a string s representing a mathematical expression.
The expression contains:
- Non-negative integers
- Operators:
+,-,*,/ - Spaces
We must evaluate the expression and return the integer result.
Division truncates toward zero.
LeetCode examples:
Input: "3+2*2"
Output: 7Input: " 3/2 "
Output: 1Input: " 3+5 / 2 "
Output: 5The expression is always valid.
Input and Output
| Item | Meaning |
|---|---|
| Input | String s containing numbers and operators |
| Output | Integer result |
| Operators | +, -, *, / |
| Division | Truncate toward zero |
| Spaces | May appear anywhere |
Function shape:
class Solution:
def calculate(self, s: str) -> int:
...Examples
Example 1:
s = "3+2*2"Multiplication has higher priority:
2 * 2 = 4
3 + 4 = 7Result:
7Example 2:
s = "14-3/2"Division first:
3 / 2 = 1Integer division truncates toward zero.
Then:
14 - 1 = 13Result:
13Example 3:
s = "18+4*5-6/2"Compute multiplication and division first:
4 * 5 = 20
6 / 2 = 3Then:
18 + 20 - 3 = 35Result:
35First Thought: Build the Full Expression
One possible idea is:
- Parse all numbers and operators.
- Build an expression tree.
- Evaluate the tree.
This works, but the problem is simpler than a full calculator parser.
We only have four operators and no parentheses.
That means we only need to handle operator precedence.
Key Insight
Addition and subtraction are low-priority operators.
Multiplication and division are high-priority operators.
We can process the expression from left to right while maintaining a stack.
The stack represents values that will eventually be added together.
Rules:
+x→ pushx-x→ push-x*x→ pop top value, multiply, push result/x→ pop top value, divide, push result
At the end:
answer = sum(stack)This works because multiplication and division are resolved immediately before later addition.
Algorithm
We scan the string character by character.
Maintain:
num: current number being builtop: previous operatorstack: intermediate values
Initially:
op = "+"This means the first number behaves like a positive number.
For every character:
If the character is a digit
Extend the current number:
num = num * 10 + int(ch)This handles multi-digit numbers.
For example:
"123"becomes:
1
12
123If the character is an operator or we reached the end
We process the previous operator stored in op.
If:
op == "+"push:
numIf:
op == "-"push:
-numIf:
op == "*"compute immediately:
stack.pop() * numIf:
op == "/"compute immediately:
int(stack.pop() / num)Then:
- Update
op - Reset
num = 0
At the end, sum the stack.
Correctness
We process the expression from left to right.
The stack always stores values that should eventually be added together.
For addition:
a + bwe simply push a, then push b.
For subtraction:
a - bwe push a, then push -b.
So subtraction becomes addition of a negative number.
Multiplication and division must happen before later additions.
When we encounter * or /, the left operand is already at the top of the stack.
So we immediately compute:
top * numor:
top / numand replace the old value.
This guarantees multiplication and division are resolved before the final sum.
Therefore, the final stack sum equals the correctly evaluated expression.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | We scan the string once |
| Space | O(n) | Stack may store many numbers |
Here, n is the length of the string.
Implementation
class Solution:
def calculate(self, s: str) -> int:
stack = []
num = 0
op = "+"
for i, ch in enumerate(s):
if ch.isdigit():
num = num * 10 + int(ch)
if ch in "+-*/" or i == len(s) - 1:
if op == "+":
stack.append(num)
elif op == "-":
stack.append(-num)
elif op == "*":
stack.append(stack.pop() * num)
elif op == "/":
stack.append(int(stack.pop() / num))
op = ch
num = 0
return sum(stack)Code Explanation
We keep building the current number:
num = num * 10 + int(ch)This supports numbers larger than one digit.
The variable op stores the operator that should be applied to num.
Initially:
op = "+"so the first number gets pushed normally.
When we hit an operator, we process the previous operation.
For example:
elif op == "*":
stack.append(stack.pop() * num)Suppose the stack top is 3 and num = 4.
Then:
3 * 4 = 12and 12 replaces the old stack value.
Division uses:
int(stack.pop() / num)because LeetCode requires truncation toward zero.
At the end:
return sum(stack)All multiplication and division are already resolved, so simple addition gives the final answer.
Testing
def run_tests():
s = Solution()
assert s.calculate("3+2*2") == 7
assert s.calculate(" 3/2 ") == 1
assert s.calculate(" 3+5 / 2 ") == 5
assert s.calculate("14-3/2") == 13
assert s.calculate("18+4*5-6/2") == 35
assert s.calculate("42") == 42
assert s.calculate("100/10*5") == 50
assert s.calculate("1-1+1") == 1
print("all tests passed")
run_tests()| Test | Why |
|---|---|
"3+2*2" | Basic precedence |
"3/2" | Integer division |
"3+5/2" | Mixed operators |
"14-3/2" | Subtraction with division |
"18+4*5-6/2" | Multiple precedence changes |
"42" | Single number |
"100/10*5" | Sequential high-priority operators |
"1-1+1" | Mixed positive and negative accumulation |