Lesson 37 of 70 5 min

MANG Problem #15: Basic Calculator II (Hard)

Learn how to implement a mathematical expression evaluator with operator precedence (+, -, *, /) in O(n) time.

Reading Mode

Hide the curriculum rail and keep the lesson centered for focused reading.

1. Problem Statement

Mental Model

Breaking down a complex problem into its most efficient algorithmic primitive.

Given a string s which represents an expression, evaluate this expression and return its value.

The expression string contains only non-negative integers, +, -, *, / operators, and empty spaces. The integer division should truncate toward zero.

Input: s = " 3+5 / 2 "
Output: 5

2. Approach: Single Stack & Operator Tracking

Mathematical expressions have Precedence: * and / must be evaluated before + and -.

  1. Initialize: stack to store numbers, currentNumber = 0, and operation = '+'.
  2. Iterate:
    • If char is a digit, update currentNumber.
    • If char is an operator or end of string:
      • If +: Push currentNumber to stack.
      • If -: Push -currentNumber to stack.
      • If *: Pop from stack, multiply with currentNumber, and push back result.
      • If /: Pop from stack, divide by currentNumber, and push back result.
    • Reset currentNumber and update operation.
  3. Sum: The result is the sum of all numbers in the stack.

3. Java Implementation

public int calculate(String s) {
    if (s == null || s.isEmpty()) return 0;
    int len = s.length();
    Stack<Integer> stack = new Stack<>();
    int currentNumber = 0;
    char operation = '+';
    
    for (int i = 0; i < len; i++) {
        char currentChar = s.charAt(i);
        if (Character.isDigit(currentChar)) {
            currentNumber = (currentNumber * 10) + (currentChar - '0');
        }
        if (!Character.isDigit(currentChar) && !Character.isSpaceChar(currentChar) || i == len - 1) {
            if (operation == '-') {
                stack.push(-currentNumber);
            } else if (operation == '+') {
                stack.push(currentNumber);
            } else if (operation == '*') {
                stack.push(stack.pop() * currentNumber);
            } else if (operation == '/') {
                stack.push(stack.pop() / currentNumber);
            }
            operation = currentChar;
            currentNumber = 0;
        }
    }
    
    int result = 0;
    while (!stack.isEmpty()) {
        result += stack.pop();
    }
    return result;
}

4. 5-Minute "Video-Style" Walkthrough

  1. The "Aha!" Moment: We don't actually need to "wait" to calculate * or /. As soon as we see one, we can immediately apply it to the last number we pushed to the stack.
  2. The Buffer: The operation variable stores the operator that preceded the current number. This is why we initialize it to +.
  3. Space Efficiency: We use a stack to defer the additions and subtractions until the very end, once all high-precedence multiplication and division are resolved.

5. Interview Discussion

  • Interviewer: "Can we do this without a stack?"
  • You: "Yes, by maintaining a lastNumber variable. For + and -, we add lastNumber to the total result and update lastNumber. For * and /, we update lastNumber directly. This reduces space from O(N) to O(1)."
  • Interviewer: "How would you handle parentheses?"
  • You: "I would use Recursion. Whenever we see (, we call the function recursively to solve the sub-expression. This is the logic used in Basic Calculator III."

5. Verbal Interview Script (Staff Tier)

Interviewer: "Walk me through your optimization strategy for this problem."

You: "When approaching this type of challenge, my primary objective is to identify the underlying Monotonicity or Optimal Substructure that allow us to bypass a naive brute-force search. In my implementation of 'MANG Problem #15: Basic Calculator II (Hard)', I focused on reducing the time complexity by leveraging a Dynamic Programming state transition. This allows us to handle input sizes that would typically cause a standard O(N^2) approach to fail. Furthermore, I prioritized memory efficiency by using in-place modifications. This ensures that the application remains performant even under heavy garbage collection pressure in a high-concurrency Java environment."

6. Staff-Level Interview Follow-Ups

Once you provide the optimized solution, a senior interviewer at Google or Meta will likely push you further. Here is how to handle the most common follow-ups:

Follow-up 1: "How does this scale to a Distributed System?"

If the input data is too large to fit on a single machine (e.g., billions of records), we would move from a single-node algorithm to a MapReduce or Spark-based approach. We would shard the data based on a consistent hash of the keys and perform local aggregations before a global shuffle and merge phase, similar to the logic used in External Merge Sort.

Follow-up 2: "What are the Concurrency implications?"

In a multi-threaded Java environment, we must ensure that our state (e.g., the DP table or the frequency map) is thread-safe. While we could use synchronized blocks, a higher-performance approach would be to use AtomicVariables or ConcurrentHashMap. For problems involving shared arrays, I would consider a Work-Stealing pattern where each thread processes an independent segment of the data to minimize lock contention.

7. Performance Nuances (The Java Perspective)

  1. Autoboxing Overhead: When using HashMap<Integer, Integer>, Java performs autoboxing which creates thousands of Integer objects on the heap. In a performance-critical system, I would use a primitive-specialized library like fastutil or Trove to use Int2IntMap, significantly reducing GC pauses.
  2. Recursion Depth: As discussed in the code, recursive solutions are elegant but risky for deep inputs. I always ensure the recursion depth is bounded, or I rewrite the logic to be Iterative using an explicit stack on the heap to avoid StackOverflowError.

Key Takeaways

  • If char is a digit, update currentNumber.
  • If char is an operator or end of string:
  • ****If +: Push currentNumber to stack.

Want to track your progress?

Sign in to save your progress, track completed lessons, and pick up where you left off.