Prerequisites

Before attempting this problem, you should be comfortable with:

  • Stack Data Structure - Used to manage both operator precedence and nested parentheses
  • String Parsing - Building multi-digit numbers and identifying operators, parentheses, and whitespace
  • Operator Precedence - Multiplication/division before addition/subtraction
  • Recursion - One approach uses recursive calls to handle nested parentheses
  • Basic Calculator II - This problem extends Basic Calculator II by adding parentheses support

1. Stack

Intuition

This problem adds parentheses to the basic calculator, which introduces nested sub-expressions. We handle this by using a stack that can hold both numbers and operators. When we see an opening parenthesis, we save the current operator and reset our state. When we see a closing parenthesis, we evaluate everything inside, then retrieve the saved operator to continue. The stack essentially lets us pause the outer expression, fully evaluate the inner one, and resume.

Algorithm

  1. Append a sentinel character @ to the string to trigger final processing.
  2. For each character:
    • If it's a digit, build the current number.
    • If it's (, push the previous operator onto the stack and reset the operator to +.
    • Otherwise (operator or ) or @):
      • Apply the previous operator to the current number (handle * and / by popping and computing immediately).
      • If it's ), sum all numbers on the stack until we hit a saved operator, then use that operator for the combined result.
      • Update the previous operator and reset the current number.
  3. Return the sum of remaining numbers on the stack.
class Solution:
    def calculate(self, s: str) -> int:
        def evaluate(x, y, operator):
            if operator == "+":
                return x
            if operator == "-":
                return -x
            if operator == "*":
                return x * y
            return int(x / y)
        
        stack = []
        curr = 0
        previous_operator = "+"
        s += "@"
        
        for c in s:
            if c.isdigit():
                curr = curr * 10 + int(c)
            elif c == "(":
                stack.append(previous_operator)
                previous_operator = "+"
            else:
                if previous_operator in "*/":
                    stack.append(evaluate(stack.pop(), curr, previous_operator))
                else:
                    stack.append(evaluate(curr, 0, previous_operator))
                
                curr = 0
                previous_operator = c
                if c == ")":
                    while type(stack[-1]) == int:
                        curr += stack.pop()
                    previous_operator = stack.pop()

        return sum(stack)

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

Where nn is the length of the expression


2. Solve Isolated Expressions With Recursion

Intuition

Parentheses naturally suggest recursion. When we encounter an opening parenthesis, we can recursively evaluate everything inside it and treat the result as a single number. This makes the problem cleaner since each recursive call handles one level of nesting. The base case is a simple expression without parentheses, which we evaluate using the same logic as Basic Calculator II.

Algorithm

  1. Append a sentinel @ to the string. Use an index variable shared across recursive calls.
  2. Define a recursive solve function:
    • Initialize a stack, current number, and previous operator (+).
    • While the index is within bounds:
      • If the character is (, increment the index and recursively call solve to get the inner result.
      • If it's a digit, build the current number.
      • Otherwise, apply the previous operator (push for +/-, compute for *//).
      • If it's ), break out of the loop.
      • Reset the number and update the operator.
    • Return the sum of the stack.
  3. Call solve starting from index 0 and return its result.
class Solution:
    def calculate(self, s: str) -> int:
        def evaluate(x, y, operator):
            if operator == "+":
                return x
            if operator == "-":
                return -x
            if operator == "*":
                return x * y
            return int(x / y)
        
        def solve(i):
            stack = []
            curr = 0
            previous_operator = "+"
            
            while i[0] < len(s):
                c = s[i[0]]
                if c == "(":
                    i[0] += 1
                    curr = solve(i)
                elif c.isdigit():
                    curr = curr * 10 + int(c)
                else:
                    if previous_operator in "*/":
                        stack.append(evaluate(stack.pop(), curr, previous_operator))
                    else:
                        stack.append(evaluate(curr, 0, previous_operator))
                     
                    if c == ")":
                        break
                    
                    curr = 0
                    previous_operator = c
                    
                i[0] += 1
            
            return sum(stack)

        s += "@"
        return solve([0])

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

Where nn is the length of the expression


Common Pitfalls

Not Resetting State When Entering Parentheses

When encountering an opening parenthesis, you must save the current operator and reset to a default state (usually +). Failing to reset causes the operator before the parenthesis to be applied incorrectly inside.

# Wrong: not resetting previous_operator when entering parenthesis
# Correct: save current operator to stack, reset to '+'

Incorrect Handling of Closing Parenthesis

When a closing parenthesis is encountered, you must sum all values inside the parentheses and then apply the saved operator from before the opening parenthesis. Missing either step produces wrong results.

Mixing Up Operator Precedence with Parentheses

Multiplication and division still have higher precedence than addition and subtraction, even inside parentheses. The parentheses only group sub-expressions; they do not change the relative precedence of operators within them.

Off-by-One Errors in Index Management with Recursion

When using recursion to handle parentheses, the index must be shared across recursive calls (e.g., using a mutable reference or array). Failing to properly advance the index after processing a sub-expression causes characters to be processed multiple times or skipped.

Forgetting the Sentinel Character

Many solutions append a sentinel character (like @) to trigger processing of the final number. Without this, the last number or sub-expression may not be evaluated.