227. Basic Calculator II
Problem Description
You are given a string s
that represents a mathematical expression containing integers and the operators +
, -
, *
, and /
. Your task is to evaluate this expression and return its value.
The expression follows these rules:
- The string contains only non-negative integers and the four operators (
+
,-
,*
,/
) - Integer division should truncate toward zero (e.g.,
7/2 = 3
and-7/2 = -3
) - The given expression is always valid
- All intermediate results will be within the range
[-2^31, 2^31 - 1]
- You cannot use built-in functions like
eval()
that evaluate strings as mathematical expressions
The expression follows standard mathematical operator precedence where multiplication and division are performed before addition and subtraction. For example, "3+2*2"
should return 7
(not 10
), and "3/2"
should return 1
.
The string may contain spaces, which should be ignored during evaluation. Your solution needs to correctly handle the operator precedence and return the final calculated result.
Intuition
The key challenge is handling operator precedence - multiplication and division must be evaluated before addition and subtraction. If we try to evaluate the expression from left to right in a single pass, we'd get incorrect results for expressions like "3+2*2"
.
Think about how we'd manually evaluate "3+5*2-6/3"
:
- First, we'd identify all the multiplication and division operations:
5*2=10
and6/3=2
- Then we'd be left with
"3+10-2"
- Finally, we'd evaluate from left to right:
3+10-2=11
This suggests we need a two-phase approach: handle *
and /
first, then handle +
and -
. But how can we do this in a single pass?
The insight is to use a stack to defer the final addition/subtraction until we've handled all multiplications and divisions. When we encounter:
- A
+
or-
: We can safely push the number to the stack (with appropriate sign) because these operations have the lowest precedence - A
*
or/
: We must immediately compute it with the previous number (top of stack) since these have higher precedence
For example, with "3+5*2"
:
- See
3
with+
sign → push3
to stack:[3]
- See
5
with+
sign → push5
to stack:[3, 5]
- See
2
with*
sign → pop5
, compute5*2=10
, push back:[3, 10]
- Sum the stack:
3+10=13
The clever part is tracking the operator that comes before each number. This tells us what to do with that number when we finish reading it. By processing multiplication/division immediately but deferring addition/subtraction to the stack, we naturally handle operator precedence in a single pass.
Solution Approach
We traverse the string s
using a stack-based approach. The key variables are:
stk
: A stack to store numbers that will be summed at the endv
: Current number being built from consecutive digitssign
: The operator before the current number (initialized as+
)
The algorithm works as follows:
-
Iterate through each character in the string:
- If it's a digit, build the current number:
v = v * 10 + int(c)
- If it's an operator (
+-*/
) or we've reached the end of the string, process the completed number
- If it's a digit, build the current number:
-
Process the number based on the previous operator (
sign
):+
: Push the number directly to the stack:stk.append(v)
-
: Push the negative of the number:stk.append(-v)
*
: Pop the top element, multiply with current number, push result:stk.append(stk.pop() * v)
/
: Pop the top element, divide by current number, push result:stk.append(int(stk.pop() / v))
-
Update for next iteration:
- Set
sign
to the current operator for the next number - Reset
v = 0
to start building the next number
- Set
-
Return the final result: Sum all elements in the stack
Example walkthrough for "3+5*2-6/3"
:
- Process
3
with sign+
: stack =[3]
- Process
5
with sign+
: stack =[3, 5]
- Process
2
with sign*
: pop5
, compute5*2=10
, stack =[3, 10]
- Process
6
with sign-
: stack =[3, 10, -6]
- Process
3
with sign/
: pop-6
, compute-6/3=-2
, stack =[3, 10, -2]
- Return sum:
3 + 10 + (-2) = 11
The stack naturally handles operator precedence: multiplication and division are computed immediately (modifying the stack top), while addition and subtraction are deferred (just pushing to stack) until the final sum.
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 walk through the expression "2+3*4-8/2"
step by step to see how our stack-based approach handles operator precedence correctly.
Initial Setup:
stk = []
(empty stack)v = 0
(current number being built)sign = '+'
(initialized as addition)
Step-by-step Process:
-
Character '2': It's a digit, so
v = 0 * 10 + 2 = 2
-
Character '+': An operator! Process the number
2
with currentsign = '+'
- Since sign is
'+'
, push2
to stack →stk = [2]
- Update
sign = '+'
for next number - Reset
v = 0
- Since sign is
-
Character '3': It's a digit, so
v = 0 * 10 + 3 = 3
-
Character '*': An operator! Process the number
3
with currentsign = '+'
- Since sign is
'+'
, push3
to stack →stk = [2, 3]
- Update
sign = '*'
for next number - Reset
v = 0
- Since sign is
-
Character '4': It's a digit, so
v = 0 * 10 + 4 = 4
-
Character '-': An operator! Process the number
4
with currentsign = '*'
- Since sign is
'*'
, pop top element3
, compute3 * 4 = 12
, push result stk = [2, 12]
- Update
sign = '-'
for next number - Reset
v = 0
- Since sign is
-
Character '8': It's a digit, so
v = 0 * 10 + 8 = 8
-
Character '/': An operator! Process the number
8
with currentsign = '-'
- Since sign is
'-'
, push-8
to stack →stk = [2, 12, -8]
- Update
sign = '/'
for next number - Reset
v = 0
- Since sign is
-
Character '2': It's a digit, so
v = 0 * 10 + 2 = 2
-
End of string: Process the final number
2
with currentsign = '/'
- Since sign is
'/'
, pop top element-8
, compute-8 / 2 = -4
, push result stk = [2, 12, -4]
- Since sign is
Final Result: Sum all elements in stack: 2 + 12 + (-4) = 10
Notice how multiplication (3*4
) and division (-8/2
) were computed immediately when we encountered the next operator, while addition and subtraction operations were deferred to the stack. This ensures correct operator precedence without needing multiple passes through the string.
Solution Implementation
1class Solution:
2 def calculate(self, s: str) -> int:
3 """
4 Evaluates a mathematical expression string containing +, -, *, / operators.
5 Follows standard operator precedence (multiplication and division before addition and subtraction).
6
7 Args:
8 s: String expression to evaluate
9
10 Returns:
11 Integer result of the expression
12 """
13 current_number = 0
14 string_length = len(s)
15 previous_operator = '+' # Initialize with '+' to handle the first number
16 stack = []
17
18 for index, char in enumerate(s):
19 # Build multi-digit numbers
20 if char.isdigit():
21 current_number = current_number * 10 + int(char)
22
23 # Process the number when we hit an operator or reach the end of string
24 # Skip spaces by checking if char is an operator
25 if index == string_length - 1 or char in '+-*/':
26 # Apply the previous operator to the current number
27 if previous_operator == '+':
28 # Push positive number to stack
29 stack.append(current_number)
30 elif previous_operator == '-':
31 # Push negative number to stack
32 stack.append(-current_number)
33 elif previous_operator == '*':
34 # Multiply with the last number in stack
35 stack.append(stack.pop() * current_number)
36 elif previous_operator == '/':
37 # Divide the last number in stack (truncate towards zero)
38 stack.append(int(stack.pop() / current_number))
39
40 # Update operator for next iteration
41 previous_operator = char
42 # Reset current number for next parsing
43 current_number = 0
44
45 # Sum all numbers in the stack to get the final result
46 return sum(stack)
47
1class Solution {
2 public int calculate(String s) {
3 // Stack to store intermediate results
4 Deque<Integer> stack = new ArrayDeque<>();
5
6 // Current operator, initialized to '+'
7 char operator = '+';
8
9 // Current number being parsed
10 int currentNumber = 0;
11
12 // Iterate through each character in the string
13 for (int i = 0; i < s.length(); i++) {
14 char currentChar = s.charAt(i);
15
16 // Build multi-digit numbers
17 if (Character.isDigit(currentChar)) {
18 currentNumber = currentNumber * 10 + (currentChar - '0');
19 }
20
21 // Process the number when we encounter an operator or reach the end
22 // Skip spaces by checking if it's an operator or end of string
23 if (i == s.length() - 1 ||
24 currentChar == '+' ||
25 currentChar == '-' ||
26 currentChar == '*' ||
27 currentChar == '/') {
28
29 // Apply the previous operator to the current number
30 if (operator == '+') {
31 stack.push(currentNumber);
32 } else if (operator == '-') {
33 stack.push(-currentNumber);
34 } else if (operator == '*') {
35 // Pop the last number and multiply with current number
36 stack.push(stack.pop() * currentNumber);
37 } else if (operator == '/') {
38 // Pop the last number and divide by current number
39 stack.push(stack.pop() / currentNumber);
40 }
41
42 // Update operator for next iteration
43 operator = currentChar;
44
45 // Reset current number for next parsing
46 currentNumber = 0;
47 }
48 }
49
50 // Sum all numbers in the stack to get the final result
51 int result = 0;
52 while (!stack.isEmpty()) {
53 result += stack.pop();
54 }
55
56 return result;
57 }
58}
59
1class Solution {
2public:
3 int calculate(string s) {
4 int currentNumber = 0;
5 int n = s.size();
6 char previousOperator = '+'; // Initialize with '+' to handle the first number
7 stack<int> numberStack;
8
9 for (int i = 0; i < n; ++i) {
10 char currentChar = s[i];
11
12 // Build multi-digit numbers
13 if (isdigit(currentChar)) {
14 currentNumber = currentNumber * 10 + (currentChar - '0');
15 }
16
17 // Process when we encounter an operator or reach the end of string
18 // Note: We also need to check i == n - 1 to process the last number
19 if (i == n - 1 || currentChar == '+' || currentChar == '-' ||
20 currentChar == '*' || currentChar == '/') {
21
22 // Apply the previous operator to the current number
23 if (previousOperator == '+') {
24 // Push positive number to stack
25 numberStack.push(currentNumber);
26 } else if (previousOperator == '-') {
27 // Push negative number to stack
28 numberStack.push(-currentNumber);
29 } else if (previousOperator == '*') {
30 // Multiply with the top of stack
31 int topValue = numberStack.top();
32 numberStack.pop();
33 numberStack.push(topValue * currentNumber);
34 } else { // previousOperator == '/'
35 // Divide the top of stack by current number
36 int topValue = numberStack.top();
37 numberStack.pop();
38 numberStack.push(topValue / currentNumber);
39 }
40
41 // Update operator for next iteration
42 previousOperator = currentChar;
43 // Reset current number for next parsing
44 currentNumber = 0;
45 }
46 }
47
48 // Sum all numbers in the stack to get the final result
49 int result = 0;
50 while (!numberStack.empty()) {
51 result += numberStack.top();
52 numberStack.pop();
53 }
54
55 return result;
56 }
57};
58
1function calculate(s: string): number {
2 let currentNumber = 0;
3 const n = s.length;
4 let previousOperator = '+'; // Initialize with '+' to handle the first number
5 const numberStack: number[] = [];
6
7 for (let i = 0; i < n; i++) {
8 const currentChar = s[i];
9
10 // Build multi-digit numbers
11 if (isDigit(currentChar)) {
12 currentNumber = currentNumber * 10 + (currentChar.charCodeAt(0) - '0'.charCodeAt(0));
13 }
14
15 // Process when we encounter an operator or reach the end of string
16 // Note: We also need to check i === n - 1 to process the last number
17 if (i === n - 1 || currentChar === '+' || currentChar === '-' ||
18 currentChar === '*' || currentChar === '/') {
19
20 // Apply the previous operator to the current number
21 if (previousOperator === '+') {
22 // Push positive number to stack
23 numberStack.push(currentNumber);
24 } else if (previousOperator === '-') {
25 // Push negative number to stack
26 numberStack.push(-currentNumber);
27 } else if (previousOperator === '*') {
28 // Multiply with the top of stack
29 const topValue = numberStack.pop()!;
30 numberStack.push(topValue * currentNumber);
31 } else { // previousOperator === '/'
32 // Divide the top of stack by current number
33 const topValue = numberStack.pop()!;
34 // Use Math.trunc for integer division (truncates towards zero)
35 numberStack.push(Math.trunc(topValue / currentNumber));
36 }
37
38 // Update operator for next iteration
39 previousOperator = currentChar;
40 // Reset current number for next parsing
41 currentNumber = 0;
42 }
43 }
44
45 // Sum all numbers in the stack to get the final result
46 let result = 0;
47 while (numberStack.length > 0) {
48 result += numberStack.pop()!;
49 }
50
51 return result;
52}
53
54// Helper function to check if a character is a digit
55function isDigit(char: string): boolean {
56 return char >= '0' && char <= '9';
57}
58
Time and Space Complexity
Time Complexity: O(n)
The algorithm iterates through the string s
exactly once using a single for loop. For each character, it performs constant time operations:
- Checking if a character is a digit:
O(1)
- Updating the value
v
:O(1)
- Performing stack operations (append/pop):
O(1)
- Arithmetic operations (+, -, *, /):
O(1)
After the loop, the sum()
function iterates through the stack once, which contains at most n
elements, adding another O(n)
operation. Therefore, the overall time complexity is O(n) + O(n) = O(n)
.
Space Complexity: O(n)
The main space consumption comes from the stack stk
. In the worst case, when the expression contains only addition and subtraction operations (e.g., "1+2+3+4+5"), every number will be pushed onto the stack. Since there can be at most n/2
numbers in a string of length n
(considering operators and digits), the stack can grow up to O(n)
size. The other variables (v
, n
, sign
, i
, c
) use constant space O(1)
. Therefore, the overall space complexity is O(n)
.
Common Pitfalls
1. Incorrect Handling of Division with Negative Numbers
The Pitfall:
The most common mistake is using //
(floor division) instead of int(a/b)
for integer division. In Python, these behave differently for negative numbers:
7 // 2 = 3
andint(7/2) = 3
(same result)-7 // 2 = -4
butint(-7/2) = -3
(different results!)
The problem requires truncation toward zero, meaning -7/2
should give -3
, not -4
.
Incorrect Code:
elif previous_operator == '/': stack.append(stack.pop() // current_number) # Wrong for negative numbers!
Correct Solution:
elif previous_operator == '/':
stack.append(int(stack.pop() / current_number)) # Truncates toward zero
2. Not Processing the Last Number
The Pitfall: Forgetting to process the last number when the string ends with a digit. The operator processing logic only triggers when encountering an operator, so the final number might be ignored.
Incorrect Code:
for index, char in enumerate(s):
if char.isdigit():
current_number = current_number * 10 + int(char)
if char in '+-*/': # Missing end-of-string check!
# Process number...
Correct Solution:
if index == string_length - 1 or char in '+-*/': # This ensures the last number is processed
3. Processing Spaces Incorrectly
The Pitfall: Treating spaces as triggers for number processing, which can cause premature evaluation or incorrect operator assignment.
Incorrect Code:
if not char.isdigit() or index == string_length - 1: # This would trigger on spaces, causing errors if previous_operator == '+': stack.append(current_number) previous_operator = char # Space gets assigned as operator!
Correct Solution: Only process when hitting actual operators or end of string, and skip spaces entirely:
if index == string_length - 1 or char in '+-*/': # Process number # Only update operator if char is actually an operator if char in '+-*/': previous_operator = char
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
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!