224. Basic Calculator
Problem Description
You need to implement a basic calculator that can evaluate a string expression containing:
- Non-negative integers
- Plus (
+
) and minus (-
) operators - Opening (
(
) and closing ()
) parentheses - Spaces (which should be ignored)
The goal is to calculate and return the result of the mathematical expression represented by the string s
.
For example:
- Input:
"1 + 1"
should return2
- Input:
"2-1 + 2"
should return3
- Input:
"(1+(4+5+2)-3)+(6+8)"
should return23
The expression is guaranteed to be valid, meaning:
- Parentheses are properly matched
- The expression follows correct mathematical syntax
- Numbers are non-negative integers
You cannot use built-in functions like eval()
that directly evaluate string expressions. You must implement the calculation logic yourself by parsing the string character by character and handling the operators and parentheses appropriately.
The main challenge is handling the parentheses correctly - when you encounter an opening parenthesis, you need to calculate the expression inside it first before continuing with the rest of the expression. Nested parentheses should also be handled properly.
Intuition
When we evaluate a mathematical expression manually, we naturally process it from left to right, keeping track of the running sum and whether we're adding or subtracting the next number. The key insight is that we need to handle two main challenges: managing the current sign (+
or -
) and dealing with parentheses.
For the signs, we can maintain a sign
variable that tells us whether to add (+1
) or subtract (-1
) the next number we encounter. When we see a number, we multiply it by the current sign and add it to our running answer.
The real complexity comes with parentheses. When we encounter an opening parenthesis (
, we're essentially starting a new sub-expression that needs to be evaluated independently. But here's the crucial observation: we need to remember what we were doing before we entered the parentheses - what was our answer so far, and what sign should be applied to the result of the parentheses?
This naturally leads us to use a stack. When we see (
, we save our current state (the answer so far and the sign that should be applied to the parentheses result) onto the stack. Then we reset our variables to start fresh for evaluating the expression inside the parentheses.
When we encounter )
, we've finished evaluating the expression inside the parentheses. Now we need to combine this result with what we had before. We pop the sign and the previous answer from the stack. The sign tells us whether the parentheses result should be added or subtracted (remember, there could be a -
before the (
), and we combine it with the previous answer.
For example, in 5 - (2 + 3)
:
- We calculate
5
first - When we see
- (
, we save5
and-1
(the sign) to the stack - We calculate
2 + 3 = 5
inside the parentheses - When we see
)
, we pop-1
and5
, then compute:5 + (-1 * 5) = 0
This stack-based approach elegantly handles nested parentheses too, as each level of nesting simply adds another pair of values to the stack.
Solution Approach
We implement the calculator using a stack to handle parentheses and maintain the calculation state. Here's how the algorithm works:
Data Structures:
stk
: A stack to save the calculation state when entering parenthesesans
: Accumulator for the current calculation result (initialized to 0)sign
: Current sign for the next number (1 for positive, -1 for negative)i
: Index pointer to traverse the string
Algorithm Steps:
-
Initialize variables: Set
ans = 0
,sign = 1
, and create an empty stack. -
Traverse the string character by character:
-
If the character is a digit:
- Use a while loop to read the complete multi-digit number
- Build the number by:
x = x * 10 + int(s[j])
- Add the number to
ans
with the appropriate sign:ans += sign * x
- Move the index to the last digit of the number
-
If the character is
'+'
:- Set
sign = 1
for the next number
- Set
-
If the character is
'-'
:- Set
sign = -1
for the next number
- Set
-
If the character is
'('
:- Push the current
ans
onto the stack (save the result before parentheses) - Push the current
sign
onto the stack (save the sign that should be applied to the parentheses result) - Reset
ans = 0
andsign = 1
to start fresh for the expression inside parentheses
- Push the current
-
If the character is
')'
:- Pop twice from the stack:
- First pop gives the sign before the parentheses
- Second pop gives the answer calculated before the parentheses
- Combine them:
ans = previous_sign * ans + previous_ans
- This applies the correct sign to the parentheses result and adds it to the previous calculation
- Pop twice from the stack:
-
If the character is a space: Simply skip it (increment index)
-
-
Return the final result: After processing all characters,
ans
contains the final result.
Example Walkthrough for "2-(3+4)"
:
- Process
2
:ans = 2
,sign = 1
- Process
-
:sign = -1
- Process
(
: Push2
and-1
to stack, resetans = 0
,sign = 1
- Process
3
:ans = 3
- Process
+
:sign = 1
- Process
4
:ans = 3 + 4 = 7
- Process
)
: Pop-1
and2
, calculateans = -1 * 7 + 2 = -5
- Return
-5
The time complexity is O(n)
where n
is the length of the string, as we process each character once. The space complexity is O(n)
in the worst case for the stack when dealing with nested parentheses.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the expression "8-(3+2)"
step by step to see how our stack-based solution works:
Initial State:
ans = 0
,sign = 1
,stack = []
,i = 0
Step 1: Character '8' (digit)
- Build number:
x = 8
- Calculate:
ans = 0 + 1 * 8 = 8
- Move to next character
Step 2: Character '-' (minus operator)
- Set
sign = -1
(next number/expression will be subtracted) - State:
ans = 8
,sign = -1
,stack = []
Step 3: Character '(' (opening parenthesis)
- Push current
ans
(8) onto stack - Push current
sign
(-1) onto stack - Reset for new sub-expression:
ans = 0
,sign = 1
- State:
ans = 0
,sign = 1
,stack = [8, -1]
Step 4: Character '3' (digit)
- Build number:
x = 3
- Calculate:
ans = 0 + 1 * 3 = 3
- State:
ans = 3
,sign = 1
,stack = [8, -1]
Step 5: Character '+' (plus operator)
- Set
sign = 1
(next number will be added) - State:
ans = 3
,sign = 1
,stack = [8, -1]
Step 6: Character '2' (digit)
- Build number:
x = 2
- Calculate:
ans = 3 + 1 * 2 = 5
- State:
ans = 5
,sign = 1
,stack = [8, -1]
Step 7: Character ')' (closing parenthesis)
- Pop sign from stack:
previous_sign = -1
- Pop answer from stack:
previous_ans = 8
- Combine:
ans = previous_sign * ans + previous_ans = -1 * 5 + 8 = 3
- State:
ans = 3
,stack = []
Final Result: 3
The key insight here is how the parentheses handling works: when we encounter -(
, we save both the current result (8) and the sign (-1) that should be applied to whatever we calculate inside the parentheses. After evaluating 3+2=5
inside the parentheses, we apply the saved sign to get -5
and add it to the saved result to get 8 + (-5) = 3
.
Solution Implementation
1class Solution:
2 def calculate(self, s: str) -> int:
3 """
4 Evaluates a mathematical expression string containing +, -, parentheses, and spaces.
5 Uses a stack-based approach to handle nested parentheses.
6
7 Args:
8 s: Input string expression
9
10 Returns:
11 Integer result of the evaluated expression
12 """
13 stack = []
14 current_result = 0
15 current_sign = 1 # 1 for positive, -1 for negative
16 index = 0
17 length = len(s)
18
19 while index < length:
20 char = s[index]
21
22 # Parse multi-digit numbers
23 if char.isdigit():
24 number = 0
25 digit_start = index
26
27 # Continue reading digits to form the complete number
28 while digit_start < length and s[digit_start].isdigit():
29 number = number * 10 + int(s[digit_start])
30 digit_start += 1
31
32 # Apply the current sign to the number and add to result
33 current_result += current_sign * number
34
35 # Adjust index to account for multi-digit parsing
36 index = digit_start - 1
37
38 # Handle addition operator
39 elif char == "+":
40 current_sign = 1
41
42 # Handle subtraction operator
43 elif char == "-":
44 current_sign = -1
45
46 # Handle opening parenthesis
47 elif char == "(":
48 # Save current state before entering new scope
49 stack.append(current_result)
50 stack.append(current_sign)
51
52 # Reset for new expression inside parentheses
53 current_result = 0
54 current_sign = 1
55
56 # Handle closing parenthesis
57 elif char == ")":
58 # Pop the sign before the parenthesis
59 prev_sign = stack.pop()
60 # Pop the result before the parenthesis
61 prev_result = stack.pop()
62
63 # Apply the sign to current result and combine with previous
64 current_result = prev_sign * current_result + prev_result
65
66 # Skip spaces implicitly (no action needed)
67
68 index += 1
69
70 return current_result
71
1class Solution {
2 public int calculate(String s) {
3 // Stack to store intermediate results and signs when encountering parentheses
4 Deque<Integer> stack = new ArrayDeque<>();
5
6 // Current sign for the next number (1 for positive, -1 for negative)
7 int currentSign = 1;
8
9 // Running result for current expression level
10 int result = 0;
11
12 int length = s.length();
13
14 // Iterate through each character in the string
15 for (int i = 0; i < length; i++) {
16 char currentChar = s.charAt(i);
17
18 if (Character.isDigit(currentChar)) {
19 // Parse multi-digit number
20 int startIndex = i;
21 int number = 0;
22
23 // Continue reading digits to form the complete number
24 while (startIndex < length && Character.isDigit(s.charAt(startIndex))) {
25 number = number * 10 + (s.charAt(startIndex) - '0');
26 startIndex++;
27 }
28
29 // Apply the sign and add to result
30 result += currentSign * number;
31
32 // Update loop counter to skip processed digits
33 i = startIndex - 1;
34
35 } else if (currentChar == '+') {
36 // Set sign for next number as positive
37 currentSign = 1;
38
39 } else if (currentChar == '-') {
40 // Set sign for next number as negative
41 currentSign = -1;
42
43 } else if (currentChar == '(') {
44 // Save current result and sign before processing parentheses
45 stack.push(result);
46 stack.push(currentSign);
47
48 // Reset for new expression inside parentheses
49 result = 0;
50 currentSign = 1;
51
52 } else if (currentChar == ')') {
53 // Pop sign and previous result from stack
54 // Multiply current result by the sign before the parentheses
55 // Add to the result that existed before the parentheses
56 result = stack.pop() * result + stack.pop();
57 }
58 // Skip spaces (no action needed for space characters)
59 }
60
61 return result;
62 }
63}
64
1class Solution {
2public:
3 int calculate(string s) {
4 stack<int> stk;
5 int result = 0; // Current result being calculated
6 int sign = 1; // Current sign: 1 for positive, -1 for negative
7 int n = s.size();
8
9 for (int i = 0; i < n; ++i) {
10 char ch = s[i];
11
12 // Process digit: build the complete number
13 if (isdigit(ch)) {
14 int num = 0;
15 while (i < n && isdigit(s[i])) {
16 num = num * 10 + (s[i] - '0');
17 ++i;
18 }
19 --i; // Adjust index since outer loop will increment
20
21 // Add number with current sign to result
22 result += sign * num;
23 }
24 // Update sign to positive
25 else if (ch == '+') {
26 sign = 1;
27 }
28 // Update sign to negative
29 else if (ch == '-') {
30 sign = -1;
31 }
32 // Start of parentheses: save current state
33 else if (ch == '(') {
34 stk.push(result); // Save current result
35 stk.push(sign); // Save sign before parentheses
36
37 // Reset for new sub-expression
38 result = 0;
39 sign = 1;
40 }
41 // End of parentheses: restore and combine with previous state
42 else if (ch == ')') {
43 int prevSign = stk.top();
44 stk.pop();
45 int prevResult = stk.top();
46 stk.pop();
47
48 // Apply sign before parentheses and combine with previous result
49 result = prevResult + prevSign * result;
50 }
51 // Skip spaces (implicit: do nothing)
52 }
53
54 return result;
55 }
56};
57
1/**
2 * Evaluates a mathematical expression string containing +, -, parentheses, and numbers
3 * @param s - The expression string to evaluate
4 * @returns The calculated result
5 */
6function calculate(s: string): number {
7 // Stack to store intermediate results and signs when encountering parentheses
8 const stack: number[] = [];
9
10 // Current sign for the next number (1 for positive, -1 for negative)
11 let currentSign: number = 1;
12
13 // Running result for the current expression level
14 let result: number = 0;
15
16 const length: number = s.length;
17
18 for (let i = 0; i < length; i++) {
19 const char: string = s[i];
20
21 // Skip whitespace characters
22 if (char === ' ') {
23 continue;
24 }
25
26 // Handle addition operator
27 if (char === '+') {
28 currentSign = 1;
29 }
30 // Handle subtraction operator
31 else if (char === '-') {
32 currentSign = -1;
33 }
34 // Handle opening parenthesis - save current state and reset
35 else if (char === '(') {
36 // Push current result to stack
37 stack.push(result);
38 // Push sign before parenthesis to stack
39 stack.push(currentSign);
40 // Reset for new sub-expression
41 result = 0;
42 currentSign = 1;
43 }
44 // Handle closing parenthesis - restore previous state
45 else if (char === ')') {
46 // Pop sign before this parenthesis group and apply it
47 result *= stack.pop()!;
48 // Pop and add the result from before this parenthesis group
49 result += stack.pop()!;
50 }
51 // Handle numeric characters
52 else {
53 // Parse multi-digit number
54 let number: number = 0;
55 let j: number = i;
56
57 // Continue reading digits to form complete number
58 while (j < length && !isNaN(Number(s[j])) && s[j] !== ' ') {
59 number = number * 10 + (s[j].charCodeAt(0) - '0'.charCodeAt(0));
60 j++;
61 }
62
63 // Apply sign and add to result
64 result += currentSign * number;
65
66 // Update index to last processed character
67 i = j - 1;
68 }
69 }
70
71 return result;
72}
73
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the string s
.
The algorithm performs a single pass through the string using a while loop that iterates from index 0
to n-1
. For each character in the string:
- Digit parsing: When encountering a digit, the inner while loop extracts the complete number. Although there's a nested loop, each character is visited at most once across all iterations, maintaining
O(n)
complexity. - Operators (
+
,-
): Constant time operations to update the sign variable. - Opening parenthesis
(
: Constant time operations to push two values onto the stack. - Closing parenthesis
)
: Constant time operations to pop two values from the stack and perform arithmetic. - Space characters are skipped in constant time.
Since each character is processed exactly once and all operations per character are O(1)
, the total time complexity is O(n)
.
Space Complexity: O(n)
, where n
is the length of the string s
.
The space complexity is determined by the stack stk
which stores intermediate results and signs when encountering parentheses. In the worst case, the string could contain nested parentheses like ((((1))))
, where the nesting depth is proportional to n
. For each opening parenthesis, two values are pushed onto the stack (the current ans
and sign
), potentially requiring O(n)
space in total. Additional variables (ans
, sign
, i
, j
, x
) use constant space O(1)
.
Common Pitfalls
1. Incorrect Multi-Digit Number Parsing
The Pitfall: A common mistake is forgetting to adjust the main loop index after parsing a multi-digit number. When you use an inner loop to read all digits of a number, the inner loop advances past multiple characters. If you don't update the outer loop's index accordingly, you'll either skip characters or process them twice.
Problematic Code Example:
# WRONG - doesn't adjust outer loop index
while index < length:
if char.isdigit():
number = 0
j = index
while j < length and s[j].isdigit():
number = number * 10 + int(s[j])
j += 1
current_result += current_sign * number
# index += 1 # This will skip characters!
The Solution: After parsing a multi-digit number, set the main loop index to point to the last digit that was processed:
if char.isdigit():
number = 0
digit_start = index
while digit_start < length and s[digit_start].isdigit():
number = number * 10 + int(s[digit_start])
digit_start += 1
current_result += current_sign * number
index = digit_start - 1 # Critical: adjust index to last processed digit
2. Stack Order Confusion When Handling Closing Parenthesis
The Pitfall:
When encountering a closing parenthesis, the order in which you pop from the stack matters. Since you push current_result
first and then current_sign
, you must pop in reverse order (LIFO). Getting this order wrong leads to incorrect calculations.
Problematic Code Example:
# WRONG - incorrect pop order elif char == ")": prev_result = stack.pop() # This actually gets the sign! prev_sign = stack.pop() # This actually gets the result! current_result = prev_sign * current_result + prev_result
The Solution: Always pop in the reverse order of how you pushed:
elif char == "(": stack.append(current_result) # Push result first stack.append(current_sign) # Push sign second elif char == ")": prev_sign = stack.pop() # Pop sign first (was pushed last) prev_result = stack.pop() # Pop result second (was pushed first) current_result = prev_sign * current_result + prev_result
3. Not Resetting State After Opening Parenthesis
The Pitfall:
When entering a new parenthesis scope, forgetting to reset current_result
and current_sign
can cause the calculation inside parentheses to inherit values from outside, leading to wrong results.
Problematic Code Example:
# WRONG - doesn't reset state elif char == "(": stack.append(current_result) stack.append(current_sign) # Forgot to reset! The next number will be added to the old current_result
The Solution: Always reset both the result accumulator and sign when starting a new parenthesis scope:
elif char == "(": stack.append(current_result) stack.append(current_sign) current_result = 0 # Reset for fresh calculation current_sign = 1 # Reset to positive sign
These pitfalls are particularly tricky because they may work correctly for simple test cases but fail on more complex expressions with nested parentheses or multi-digit numbers.
What's the relationship between a tree and a graph?
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
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
Want a Structured Path to Master System Design Too? Don’t Miss This!