Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

772. Basic Calculator III - Leetcode Solution

Problem Description

The Basic Calculator III problem asks you to implement a calculator that can evaluate a string expression containing non-negative integers, the operators +, -, *, /, and parentheses ( and ). The expression can have spaces, which should be ignored. You must respect the standard operator precedence: multiplication and division have higher precedence than addition and subtraction, and parentheses override precedence. The division should truncate toward zero.

  • Input: a string s containing the arithmetic expression.
  • Output: an integer result after evaluating the expression.
  • You can assume the input is always a valid expression.
  • You may not use built-in expression evaluators or the eval() function.

Thought Process

At first glance, this problem seems similar to parsing arithmetic expressions, but with the added challenge of handling nested parentheses and operator precedence. A brute-force approach might involve converting the expression into a postfix notation (Reverse Polish Notation) and then evaluating it, but that can be complex to implement, especially with nested parentheses.

Alternatively, we can process the string character by character and use a stack to manage numbers and operators. When we encounter an open parenthesis, we can recursively evaluate the sub-expression inside. Handling operator precedence is crucial: multiplication and division must be evaluated before addition and subtraction. By using a stack, we can defer addition and subtraction until all higher-precedence operations are handled.

The key is to process the expression in a single pass, using stacks to manage numbers and sub-expressions, and to apply operators as soon as their operands are known and precedence allows.

Solution Approach

We will use a stack-based approach to evaluate the expression:

  1. Initialize: Use a stack to store numbers and intermediate results. Set a variable num to build multi-digit numbers and sign to track the most recent operator (default to '+').
  2. Iterate through the string: For each character:
    • If it's a digit, build the current number.
    • If it's an open parenthesis (, recursively evaluate the sub-expression inside the parentheses.
    • If it's an operator or the end of the string, apply the previous operator to the current number and push the result onto the stack:
      • '+': Push num onto the stack.
      • '-': Push -num onto the stack.
      • '*': Pop the top of the stack, multiply by num, and push the result.
      • '/': Pop the top of the stack, perform integer division by num (truncate toward zero), and push the result.
    • Update sign to the current operator and reset num to zero.
  3. Parentheses Handling: When a ( is encountered, recursively call the evaluation function starting from the next character, and use the result as the current num. When a ) is encountered, return the sum of the stack up to that point.
  4. Final Result: After processing the entire string (or sub-expression), sum all values in the stack to get the final answer.

This approach ensures that operator precedence and parentheses are handled correctly, and that the expression is evaluated in a single pass using recursion and stacks.

Example Walkthrough

Consider the input: "2*(3+(4*5))"

  1. Start with num = 0, sign = +, stack = []
  2. Read 2: num = 2
  3. Read *: Apply previous + sign, push 2 to stack. Set sign = *, num = 0
  4. Read (: Start evaluating sub-expression 3+(4*5)
  5. Read 3: num = 3
  6. Read +: Apply + sign, push 3 to stack. Set sign = +, num = 0
  7. Read (: Start evaluating sub-expression 4*5
  8. Read 4: num = 4
  9. Read *: Apply + sign, push 4 to stack. Set sign = *, num = 0
  10. Read 5: num = 5
  11. End of parenthesis: Apply * sign, pop 4 from stack, push 4*5 = 20
  12. End of parenthesis: Stack for this sub-expression is [3, 20], sum is 23
  13. Back to main: Apply * sign, pop 2 from stack, push 2*23 = 46
  14. End: Final stack is [46], sum is 46

The correct result is 46.

Time and Space Complexity

  • Brute-force (Convert to Postfix):
    • Time: O(n) for conversion + O(n) for evaluation = O(n)
    • Space: O(n) for stack and output arrays
  • Optimized (Stack + Recursion):
    • Time: O(n), since each character is processed once
    • Space: O(n) for the stack and recursion stack (in the worst case, deeply nested parentheses)

The optimized approach is efficient and suitable for expressions up to thousands of characters.

Summary

We tackled the Basic Calculator III problem by leveraging a stack-based approach with recursion to handle nested parentheses and operator precedence. By processing the expression in a single pass and applying operators as soon as their operands are ready, we efficiently evaluate even complex expressions. This method is both elegant and practical, demonstrating a powerful pattern for parsing and evaluating arithmetic expressions without relying on built-in evaluators.

Code Implementation

class Solution:
    def calculate(self, s: str) -> int:
        def helper(it):
            num = 0
            stack = []
            sign = '+'
            while True:
                try:
                    c = next(it)
                except StopIteration:
                    break
                if c == ' ':
                    continue
                if c.isdigit():
                    num = num * 10 + int(c)
                if c == '(':
                    num = helper(it)
                if (not c.isdigit() and c != ' ') or c == ')':
                    if sign == '+':
                        stack.append(num)
                    elif sign == '-':
                        stack.append(-num)
                    elif sign == '*':
                        stack.append(stack.pop() * num)
                    elif sign == '/':
                        prev = stack.pop()
                        stack.append(int(prev / num))
                    num = 0
                    sign = c
                if c == ')':
                    break
            return sum(stack)
        return helper(iter(s))
    
class Solution {
public:
    int calculate(string s) {
        int i = 0;
        return helper(s, i);
    }
private:
    int helper(const string& s, int& i) {
        stack<int> stk;
        int num = 0;
        char sign = '+';
        for (; i < s.size(); ++i) {
            char c = s[i];
            if (c == ' ') continue;
            if (isdigit(c)) {
                num = num * 10 + (c - '0');
            }
            if (c == '(') {
                ++i;
                num = helper(s, i);
            }
            if ((!isdigit(c) && c != ' ') || i == s.size() - 1 || c == ')') {
                if (sign == '+') stk.push(num);
                else if (sign == '-') stk.push(-num);
                else if (sign == '*') {
                    int t = stk.top(); stk.pop();
                    stk.push(t * num);
                }
                else if (sign == '/') {
                    int t = stk.top(); stk.pop();
                    stk.push(t / num);
                }
                sign = c;
                num = 0;
            }
            if (c == ')') break;
        }
        int res = 0;
        while (!stk.empty()) {
            res += stk.top();
            stk.pop();
        }
        return res;
    }
};
    
class Solution {
    public int calculate(String s) {
        int[] idx = new int[1];
        return helper(s, idx);
    }
    private int helper(String s, int[] idx) {
        Deque<Integer> stack = new ArrayDeque<>();
        int num = 0;
        char sign = '+';
        while (idx[0] < s.length()) {
            char c = s.charAt(idx[0]);
            if (c == ' ') {
                idx[0]++;
                continue;
            }
            if (Character.isDigit(c)) {
                num = num * 10 + (c - '0');
            }
            if (c == '(') {
                idx[0]++;
                num = helper(s, idx);
            }
            if (!Character.isDigit(c) && c != ' ' || idx[0] == s.length() - 1 || c == ')') {
                if (sign == '+') stack.push(num);
                else if (sign == '-') stack.push(-num);
                else if (sign == '*') stack.push(stack.pop() * num);
                else if (sign == '/') stack.push(stack.pop() / num);
                sign = c;
                num = 0;
            }
            if (c == ')') {
                idx[0]++;
                break;
            }
            idx[0]++;
        }
        int res = 0;
        while (!stack.isEmpty()) res += stack.pop();
        return res;
    }
}
    
var calculate = function(s) {
    let i = 0;
    function helper() {
        let stack = [];
        let num = 0;
        let sign = '+';
        while (i < s.length) {
            let c = s[i];
            if (c === ' ') {
                i++;
                continue;
            }
            if (/\d/.test(c)) {
                num = num * 10 + parseInt(c);
            }
            if (c === '(') {
                i++;
                num = helper();
            }
            if ((isNaN(parseInt(c)) && c !== ' ') || i === s.length - 1 || c === ')') {
                if (sign === '+') stack.push(num);
                else if (sign === '-') stack.push(-num);
                else if (sign === '*') stack.push(stack.pop() * num);
                else if (sign === '/') stack.push(parseInt(stack.pop() / num));
                sign = c;
                num = 0;
            }
            if (c === ')') {
                i++;
                break;
            }
            i++;
        }
        return stack.reduce((a, b) => a + b, 0);
    }
    return helper();
};