Editorial
Core insight
Basic Calculator III · Stack Patterns
Core Insight for Basic Calculator III
The key intuition for solving LeetCode 772 efficiently is to treat every expression (or sub-expression within parentheses) as a sequence of additions.
- Reduction to Summation: We can treat subtraction
A - BasA + (-B). This allows us to reduce the final step of any scope to simply summing up the stack. - Greedy Evaluation of High Precedence: When we encounter multiplication
*or division/, we cannot wait. These operators bind tighter than+or-. We must pop the top value from our stack, perform the operation with the current number, and push the result back. - Delayed Evaluation of Low Precedence: When we encounter
+or-, we push the previous number onto the stack and update our "pending operator." We do not evaluate the sum yet because a future*or/might need the current number. - Recursion for Parentheses: When we see a
(, we are entering a sub-problem. This sub-problem is identical to the main problem but ends at). We can recursively call our solver (utilizing the system call stack) to evaluate the sub-expression and return a single integer.
Visual Description:
Imagine a stack representing the current "summation terms."
If the expression is 2 + 3 * 4, the stack evolves as follows:
- Read
2, operator is+. Push2. Stack:[2]. - Read
3, operator is*. Push3. Stack:[2, 3]. - Read
4. Previous operator was*. Pop3, calculate3 * 4 = 12. Push12. Stack:[2, 12]. - End of string. Sum the stack:
2 + 12 = 14.
The stack effectively isolates terms that are separated by + signs, while * and / merge terms immediately before they are finalized on the stack.
Deep dive
Full breakdown
Full reasoning, pitfalls, and implementation flow
LeetCode 772: Basic Calculator III Solution & Explanation
Problem Overview
TL;DR: The optimal solution uses recursion (implicit stack) to handle parentheses and an explicit stack to handle operator precedence, reducing the entire expression to a sum of integers.
The LeetCode 772 problem, Basic Calculator III, asks us to evaluate a mathematical expression string that includes non-negative integers, basic operators (+, -, *, /), and parentheses ( ). The evaluation must respect standard order of operations (multiplication and division before addition and subtraction) and handle nested parentheses. Integer division must truncate toward zero.
This is a comprehensive interview question because it combines the logic of Basic Calculator I (parentheses) and Basic Calculator II (precedence).
Brute Force Approach for Basic Calculator III
A naive or brute force approach might attempt to solve the expression by repeatedly scanning the string to find the operation with the highest precedence, executing it, and replacing that part of the string with the result.
- Find the deepest set of parentheses.
- Inside those parentheses, scan for
*or/. - Perform the operation and create a new string with the result substituted.
- Repeat until no
*or/remain in that scope, then process+and-. - Remove the parentheses and repeat the process for the outer scope.
Pseudo-code:
while string has operators:
find inner-most (...)
solve multiplication/division inside
solve addition/subtraction inside
replace (...) with result in string
return resultWhy it fails: This approach has a time complexity of O(N²) or worse because string concatenation and replacement are expensive operations () that are performed repeatedly. For a string length of , this will likely result in a Time Limit Exceeded (TLE) error. Furthermore, implementing the string parsing logic correctly without bugs is tedious and error-prone during an interview.
Algorithm Strategy: Stack Patterns
We will implement a recursive solution where each level of recursion solves the expression within a pair of parentheses (or the entire string).
State Maintenance:
- Stack: Stores integers to be summed at the end of the current scope.
- Current Number (
num): Builds the multi-digit integer currently being parsed. - Last Operator (
op): Tracks the operator that precedes thenum. Initialized to+.
Constraints & Boundaries:
- The recursion handles the boundaries of
(and). - The loop terminates when we reach the end of the string or a closing parenthesis
).
Logic:
- Iterate through the string (or a shared index/iterator).
- If the character is a digit, update
num. - If the character is
(, recursively call the function to evaluate the sub-expression. The result becomes ournum. - If the character is an operator
+,-,*,/or a closing parenthesis)or the end of the string:- Apply
opto the currentnumand the top of the stack.+: Pushnum.-: Push-num.*: Poptop, pushtop * num./: Poptop, pushtop / num(truncate toward zero).
- Update
opto the current character. - Reset
numto 0.
- Apply
- If the character is
), break the loop. - Return the sum of elements in the stack.
Execution Flow
Let's trace 3 + 2 * ( 5 - 1 )
- Start Main Scope:
op = '+',stack = []. - Parse
3.opis+. Push3.stack = [3]. Updateop = '+'. - Parse
2.opis+. Push2.stack = [3, 2]. Updateop = '*'. - Encounter
(. Recurse.- Sub-Scope:
op = '+',stack = []. - Parse
5.opis+. Push5.stack = [5]. Updateop = '-'. - Parse
1.opis-. Push-1.stack = [5, -1]. Updateop = ')'. - Encounter
). Break loop. - Return sum of stack:
5 + (-1) = 4.
- Sub-Scope:
- Back to Main Scope: The recursion returned
4. So effectively,num = 4. - Previous
opwas*. Pop2from stack. Calculate2 * 4 = 8. Push8.stack = [3, 8]. - End of string.
- Return sum of stack:
3 + 8 = 11.
Proof of Correctness
The algorithm's correctness relies on the invariant that the stack always contains terms that are ready to be added.
- Base Case: Single numbers are pushed directly (or negated for subtraction).
- Inductive Step (Precedence): Multiplicative operators (
*,/) combine the current number with the last pushed number immediately. This ensures standard order of operations because the result is placed back on the stack as a single term, effectively treatinga + b * casa + (b*c). - Inductive Step (Parentheses): Recursion treats a sub-expression
(...)as a single atomic number. Since the sub-expression is evaluated using the same logic, it correctly returns the evaluated integer, maintaining the structural integrity of the expression.
Thus, at the end of any scope, the stack contains a list of values [v1, v2, ..., vn] representing v1 + v2 + ... + vn.
Pattern Reuse Notes
The Stack - Expression Evaluation pattern is highly reusable. Understanding the mechanism of "delaying evaluation" using a stack is key for these related problems:
- LeetCode 150: Evaluate Reverse Polish Notation: This is the "backend" of the calculator. It evaluates expressions already in postfix format.
- LeetCode 224: Basic Calculator: Handles
+,-, and(). It is a subset of 772. The logic is identical, though strictly handling signs is sufficient without a full multiplication stack. - LeetCode 227: Basic Calculator II: Handles
+,-,*,/but no parentheses. This is exactly the logic inside one "scope" of Basic Calculator III.
Feynman coach · Before you peek at the code
Can you explain the core insight in your own words?
If you can explain it before reading code, you’re far more likely to recall it under interview pressure.
Reference Implementation
C++ Solution for LeetCode 772
1class Solution {
2public:
3 int calculate(string s) {
4 int i = 0;
5 return parse(s, i);
6 }
7
8private:
9 int parse(const string& s, int& i) {
10 vector<long> stack;
11 char op = '+';
12 long num = 0;
13
14 while (i < s.length()) {
15 char c = s[i++];
16
17 if (isdigit(c)) {
18 num = num * 10 + (c - '0');
19 } else if (c == '(') {
20 num = parse(s, i);
21 }
22
23 // If c is an operator, or ')', or we are at the end, process the last number
24 // Note: We check i < s.length() inside the loop, but need to process
25 // the last number if we hit the end.
26 // A trick is to process if !isdigit(c) and c != ' ' or i == s.length()
27 // However, since we incremented i, we look at the previous char logic implicitly.
28 // Let's stick to the standard check:
29 if ((!isdigit(c) && c != ' ') || i == s.length()) { // Check current char (which is s[i-1] now due to i++)
30 // Wait, simpler logic: check the char we just read
31 // If the char we just read was '(', we already recursed and have num.
32 // If it was ')', we process and break.
33 }
34
35 // Correct logic flow for loop:
36 // The check needs to happen if c is an operator or end of block
37 // Since we incremented i, c is the character we are processing.
38 if ((!isdigit(c) && c != ' ') || i > s.length()) {
39 // Wait, logic correction: standard loop structure is better.
40 // Let's rewind to the standard clean pattern.
41 }
42 }
43 return 0;
44 }
45};
46
47// Re-writing strictly clean C++ implementation
48class Solution {
49public:
50 int calculate(string s) {
51 int i = 0;
52 return helper(s, i);
53 }
54
55 int helper(const string& s, int& i) {
56 vector<long> stack;
57 char op = '+';
58 long num = 0;
59
60 while (i < s.length()) {
61 char c = s[i++];
62
63 if (isdigit(c)) {
64 num = num * 10 + (c - '0');
65 } else if (c == '(') {
66 num = helper(s, i);
67 }
68
69 // Process if operator, closing parenthesis, or end of string
70 if (i == s.length() || c == '+' || c == '-' || c == '*' || c == '/' || c == ')') {
71 if (op == '+') {
72 stack.push_back(num);
73 } else if (op == '-') {
74 stack.push_back(-num);
75 } else if (op == '*') {
76 long last = stack.back();
77 stack.pop_back();
78 stack.push_back(last * num);
79 } else if (op == '/') {
80 long last = stack.back();
81 stack.pop_back();
82 // Integer division truncates toward zero in C++
83 stack.push_back(last / num);
84 }
85
86 op = c;
87 num = 0;
88
89 if (c == ')') break;
90 }
91 }
92
93 long result = 0;
94 for (long n : stack) result += n;
95 return (int)result;
96 }
97};Java Solution for LeetCode 772
1import java.util.*;
2
3class Solution {
4 private int i = 0; // Global index to track position across recursion
5
6 public int calculate(String s) {
7 i = 0;
8 return helper(s);
9 }
10
11 private int helper(String s) {
12 Stack<Integer> stack = new Stack<>();
13 char op = '+';
14 int num = 0;
15
16 while (i < s.length()) {
17 char c = s.charAt(i++);
18
19 if (Character.isDigit(c)) {
20 num = num * 10 + (c - '0');
21 } else if (c == '(') {
22 num = helper(s);
23 }
24
25 // If operator, ')', or end of string (handled by loop condition + internal check)
26 // We check if the character is meaningful for processing logic
27 if (i >= s.length() || c == '+' || c == '-' || c == '*' || c == '/' || c == ')') {
28 if (op == '+') {
29 stack.push(num);
30 } else if (op == '-') {
31 stack.push(-num);
32 } else if (op == '*') {
33 stack.push(stack.pop() * num);
34 } else if (op == '/') {
35 stack.push(stack.pop() / num);
36 }
37
38 op = c;
39 num = 0;
40
41 if (c == ')') break;
42 }
43 }
44
45 int result = 0;
46 for (int n : stack) {
47 result += n;
48 }
49 return result;
50 }
51}Python Solution for LeetCode 772
1class Solution:
2 def calculate(self, s: str) -> int:
3 i = 0
4
5 def parse():
6 nonlocal i
7 stack = []
8 op = '+'
9 num = 0
10
11 while i < len(s):
12 c = s[i]
13 i += 1
14
15 if c.isdigit():
16 num = num * 10 + int(c)
17 elif c == '(':
18 num = parse()
19
20 # Check bounds or if char is an operator/parenthesis
21 # We check i >= len(s) inside because we just incremented i
22 if i >= len(s) or c in '+-*/)':
23 if op == '+':
24 stack.append(num)
25 elif op == '-':
26 stack.append(-num)
27 elif op == '*':
28 stack.append(stack.pop() * num)
29 elif op == '/':
30 # Python division floors (e.g., -3 // 2 = -2)
31 # We need truncation towards zero (e.g., int(-3 / 2) = -1)
32 stack.append(int(stack.pop() / num))
33
34 op = c
35 num = 0
36
37 if c == ')':
38 break
39
40 return sum(stack)
41
42 return parse()