Basic Calculator II
Description
You get a string s holding an arithmetic expression: non-negative integers joined by +, -, * and /, with any number of spaces between them. Return its value.
The usual rules apply. * and / are done before + and -, and operators at the same level go left to right. Division between integers truncates toward zero, so 7 / 2 is 3.
The expression is always valid, and every value along the way fits in a 32-bit signed integer. You may not use a built-in function that evaluates a string as code, such as eval().
Examples
Example 1
Input: s = "3+2*2"
Output: 7
Explanation: Multiply first: 2 * 2 = 4, then 3 + 4 = 7.
Example 2
Input: s = " 3/2 "
Output: 1
Explanation: 3 / 2 is 1.5, truncated toward zero to 1.
Example 3
Input: s = " 3+5 / 2 "
Output: 5
Explanation: Divide first: 5 / 2 = 2, then 3 + 2 = 5.
Example 4
Input: s = "14-3*4/5"
Output: 12
Explanation: Multiply and divide left to right: 3 * 4 = 12, then 12 / 5 = 2, truncated. Then 14 - 2 = 12.
Constraints
- 1 ≤ s.length ≤ 3 × 10^5
- s consists of integers and operators (
+,-,*,/) separated by some number of spaces - s represents a valid expression
- All the integers in the expression are non-negative integers in the range [0, 2^31 - 1]
- The answer is guaranteed to fit in a 32-bit integer
Editorial
Brute Force
Intuition
Do it the way you would on paper. Split the string into numbers and operators. While a * or / is left, apply the first one: its two numbers and the operator become one number. Then do the same with + and -. The one number left is the answer.
Going left to right within a level matters: 12 / 2 * 3 is 18, and doing the * first would give 2.
Step-by-Step Explanation
Let's trace s = "14-3*4/5":
- Split the string into tokens:
14 - 3 * 4 / 5, 4 numbers and 3 operators. - The first
*or/is3 * 4, which is 12. Now14 - 12 / 5. - The first
*or/is12 / 5, which is 2.4, truncated to 2. Now14 - 2. - No
*or/is left, so now+and-. The first one is14 - 2, which is 12. Only 12 is left: the answer.
Apply One Operator at a Time — The tokens about to combine are amber; the number they become is green.
Algorithm
- Read
sonce, collecting the numbers innumsand the operators inops, so thatops[k]sits betweennums[k]andnums[k + 1]. - For the level
*/, then the level+-: walkkalongops. Ifops[k]is in the level, replacenums[k]withnums[k] ops[k] nums[k + 1]and deletenums[k + 1]andops[k]; the next operator has moved into placek, so checkkagain. Otherwise move on. - Return
nums[0].
Code
class Solution {
public:
int calculate(string s) {
vector<long long> nums; // ops[k] sits between nums[k] and nums[k + 1]
vector<char> ops;
long long num = 0;
for (int i = 0; i < (int)s.size(); i++) {
char c = s[i];
if (isdigit(c)) num = num * 10 + (c - '0');
bool isOp = c == '+' || c == '-' || c == '*' || c == '/';
if (isOp || i == (int)s.size() - 1) { // a number ends here
nums.push_back(num);
if (isOp) ops.push_back(c);
num = 0;
}
}
for (string level : {"*/", "+-"}) { // the tighter level first
size_t k = 0;
while (k < ops.size()) {
if (level.find(ops[k]) != string::npos) { // the first operator of this level still there
nums[k] = combine(nums[k], ops[k], nums[k + 1]);
nums.erase(nums.begin() + k + 1); // everything after it moves one place left
ops.erase(ops.begin() + k);
} else {
k++;
}
}
}
return (int)nums[0];
}
private:
long long combine(long long a, char op, long long b) {
if (op == '+') return a + b;
if (op == '-') return a - b;
if (op == '*') return a * b;
return a / b; // truncates toward zero
}
};
class Solution:
def calculate(self, s: str) -> int:
nums, ops = [], [] # ops[k] sits between nums[k] and nums[k + 1]
num = 0
for i, c in enumerate(s):
if c.isdigit():
num = num * 10 + int(c)
if c in "+-*/" or i == len(s) - 1: # a number ends here
nums.append(num)
if c in "+-*/":
ops.append(c)
num = 0
for level in ("*/", "+-"): # the tighter level first
k = 0
while k < len(ops):
if ops[k] in level: # the first operator of this level still there
nums[k] = self.combine(nums[k], ops[k], nums[k + 1])
del nums[k + 1] # everything after it moves one place left
del ops[k]
else:
k += 1
return nums[0]
def combine(self, a, op, b):
if op == "+":
return a + b
if op == "-":
return a - b
if op == "*":
return a * b
return int(a / b) # truncate toward zero
class Solution {
public int calculate(String s) {
List<Long> nums = new ArrayList<>(); // ops.get(k) sits between nums k and k + 1
List<Character> ops = new ArrayList<>();
long num = 0;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (Character.isDigit(c)) num = num * 10 + (c - '0');
boolean isOp = "+-*/".indexOf(c) >= 0;
if (isOp || i == s.length() - 1) { // a number ends here
nums.add(num);
if (isOp) ops.add(c);
num = 0;
}
}
for (String level : new String[] {"*/", "+-"}) { // the tighter level first
int k = 0;
while (k < ops.size()) {
if (level.indexOf(ops.get(k)) >= 0) { // the first operator of this level still there
nums.set(k, combine(nums.get(k), ops.get(k), nums.get(k + 1)));
nums.remove(k + 1); // everything after it moves one place left
ops.remove(k);
} else {
k++;
}
}
}
return (int) (long) nums.get(0);
}
private long combine(long a, char op, long b) {
if (op == '+') return a + b;
if (op == '-') return a - b;
if (op == '*') return a * b;
return a / b; // truncates toward zero
}
}
var calculate = function(s) {
const nums = [], ops = []; // ops[k] sits between nums[k] and nums[k + 1]
let num = 0;
for (let i = 0; i < s.length; i++) {
const c = s[i];
if (c >= "0" && c <= "9") num = num * 10 + Number(c);
const isOp = "+-*/".includes(c);
if (isOp || i === s.length - 1) { // a number ends here
nums.push(num);
if (isOp) ops.push(c);
num = 0;
}
}
for (const level of ["*/", "+-"]) { // the tighter level first
let k = 0;
while (k < ops.length) {
if (level.includes(ops[k])) { // the first operator of this level still there
nums[k] = combine(nums[k], ops[k], nums[k + 1]);
nums.splice(k + 1, 1); // everything after it moves one place left
ops.splice(k, 1);
} else {
k++;
}
}
}
return nums[0];
};
function combine(a, op, b) {
if (op === "+") return a + b;
if (op === "-") return a - b;
if (op === "*") return a * b;
return Math.trunc(a / b); // truncate toward zero
}
Complexity Analysis
Time Complexity: O(n²)
Each operator applied deletes from the middle of a list, and everything after it moves one place left: O(n) per operator, for up to n / 2 operators.
Space Complexity: O(n)
The lists of numbers and operators.
Why This Approach Is Not Efficient
With 3 × 10^5 characters there can be 1.5 × 10^5 operators, and every one of them shifts the rest of the list: about 10^10 moves (over the time limit in Python and JavaScript).
Look at when a * or / can be applied: as soon as the number after it is read. Nothing to its right binds tighter, and the * and / before it in the same term are already done. Only + and - have to wait, and they wait in a simple way: their terms are added up at the end. So read the string once, apply each * and / on the spot, and keep the terms on a stack.
Better Approach - One Pass With a Stack of Terms
Intuition
Split the expression at + and - and it falls into terms: 14-3*4/5 has the terms 14 and -3*4/5. Every * and / belongs to one term, and the answer is the sum of the terms.
Read s left to right and remember op, the operator before the number being read (an unwritten + before the first). When a number ends, op says what to do with it: after +, push it as a new term; after -, push its negative; after * or /, combine it with the term on top of the stack. At the end, add up the stack.
Step-by-Step Explanation
Let's trace s = "14-3*4/5":
- 14 comes after
+: push 14. The stack holds 14. - 3 comes after
-: push −3. The stack holds 14, −3. - 4 comes after
*: pop −3, push −3 × 4 = −12. The stack holds 14, −12. - 5 comes after
/: pop −12, push −12 / 5 = −2, truncated toward zero. The stack holds 14, −2. - That's the end of s. Add up the terms: 14 + (−2) = 12.
The −3 was pushed as a negative, so −3 × 4 / 5 truncates to −2, the same as −(12 / 5): truncating toward zero treats both signs alike.
Calculating 14-3*4/5 — The number being applied is highlighted in s; the stack holds the terms to add up.
Algorithm
- Start with an empty stack,
num = 0andop = '+'. - For each index
i: ifs[i]is a digit,num = num * 10 + digit. - If
s[i]is an operator, oriis the last index, the number has ended. Applyopto it: pushnum, push-num, or pop the top and push it times or divided bynum. Thenop = s[i]andnum = 0. - Return the sum of the stack.
Code
class Solution {
public:
int calculate(string s) {
vector<long long> terms; // the numbers to add up at the end
long long num = 0;
char op = '+'; // the operator before the number being read
for (int i = 0; i < (int)s.size(); i++) {
char c = s[i];
if (isdigit(c)) num = num * 10 + (c - '0'); // a number can have several digits
if ((!isdigit(c) && c != ' ') || i == (int)s.size() - 1) { // an operator, or the end
if (op == '+') terms.push_back(num);
else if (op == '-') terms.push_back(-num);
else if (op == '*') terms.back() *= num;
else terms.back() /= num; // truncates toward zero
op = c;
num = 0;
}
}
long long sum = 0;
for (long long t : terms) sum += t;
return (int)sum;
}
};
class Solution:
def calculate(self, s: str) -> int:
terms, num, op = [], 0, "+" # terms: the numbers to add up at the end
for i, c in enumerate(s):
if c.isdigit():
num = num * 10 + int(c) # a number can have several digits
if c in "+-*/" or i == len(s) - 1: # an operator, or the end: apply the one before this number
if op == "+":
terms.append(num)
elif op == "-":
terms.append(-num)
elif op == "*":
terms.append(terms.pop() * num)
else:
terms.append(int(terms.pop() / num)) # truncate toward zero
op, num = c, 0
return sum(terms)
class Solution {
public int calculate(String s) {
Deque<Integer> terms = new ArrayDeque<>(); // the numbers to add up at the end
int num = 0;
char op = '+'; // the operator before the number being read
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (Character.isDigit(c)) num = num * 10 + (c - '0'); // a number can have several digits
if ((!Character.isDigit(c) && c != ' ') || i == s.length() - 1) { // an operator, or the end
if (op == '+') terms.push(num);
else if (op == '-') terms.push(-num);
else if (op == '*') terms.push(terms.pop() * num);
else terms.push(terms.pop() / num); // truncates toward zero
op = c;
num = 0;
}
}
int sum = 0;
for (int t : terms) sum += t;
return sum;
}
}
var calculate = function(s) {
const terms = []; // the numbers to add up at the end
let num = 0;
let op = "+"; // the operator before the number being read
for (let i = 0; i < s.length; i++) {
const c = s[i];
if (c >= "0" && c <= "9") num = num * 10 + Number(c); // a number can have several digits
if ("+-*/".includes(c) || i === s.length - 1) { // an operator, or the end
if (op === "+") terms.push(num);
else if (op === "-") terms.push(-num);
else if (op === "*") terms.push(terms.pop() * num);
else terms.push(Math.trunc(terms.pop() / num)); // truncate toward zero
op = c;
num = 0;
}
}
return terms.reduce((a, b) => a + b, 0);
};
Complexity Analysis
Time Complexity: O(n)
Each character is read once, and each number does one push, or one pop and one push.
Space Complexity: O(n)
Up to one term per number on the stack.
Why This Approach Is Not Efficient
The stack keeps every term until the end, which is O(n) extra space.
But it only ever changes at the top: * and / rewrite the top term, and + or - push a new one. A term below the top is never touched again; it just waits to be added. So add it in the moment it's covered, and the stack shrinks to two numbers.
Optimal Approach - Running Sum and Last Term
Intuition
Keep two numbers in place of the stack: result, the sum of the terms that are finished, and last, the term still being built (the stack's top).
A number after * or / joins last. A number after + or - starts a new term, which means the old last is finished: add it to result, then set last to the new number (negated after -). At the end, last is finished too, so the answer is result + last.
Step-by-Step Explanation
Let's trace s = "14-3*4/5":
- 14 starts the first term,
last = 14. - 3 comes after
-: a new term starts, soresultbecomes 0 + 14 = 14, andlastis −3. - 4 comes after
*:lastbecomes −3 × 4 = −12. - 5 comes after
/:lastbecomes −12 / 5 = −2, truncated toward zero. - That's the end of s: the answer is
result + last= 14 + (−2) = 12.
Keep the Sum and the Last Term — Green characters are in result; the highlighted ones are the term in last.
Algorithm
- Start with
result = 0,last = 0,num = 0andop = '+'. - For each index
i: ifs[i]is a digit,num = num * 10 + digit. - If
s[i]is an operator, oriis the last index: after+or-, addlasttoresultand setlasttonumor-num; after*or/, multiply or dividelastbynum. Thenop = s[i]andnum = 0. - Return
result + last.
Code
class Solution {
public:
int calculate(string s) {
long long result = 0; // the sum of the finished terms
long long last = 0; // the term still being built
long long num = 0;
char op = '+'; // the operator before the number being read
for (int i = 0; i < (int)s.size(); i++) {
char c = s[i];
if (isdigit(c)) num = num * 10 + (c - '0');
if ((!isdigit(c) && c != ' ') || i == (int)s.size() - 1) { // an operator, or the end
if (op == '+' || op == '-') {
result += last; // a new term starts, so the last one is finished
last = op == '+' ? num : -num;
} else if (op == '*') {
last *= num;
} else {
last /= num; // truncates toward zero
}
op = c;
num = 0;
}
}
return (int)(result + last);
}
};
class Solution:
def calculate(self, s: str) -> int:
result = 0 # the sum of the finished terms
last = 0 # the term still being built
num, op = 0, "+" # op: the operator before the number being read
for i, c in enumerate(s):
if c.isdigit():
num = num * 10 + int(c)
if c in "+-*/" or i == len(s) - 1: # an operator, or the end
if op in "+-":
result += last # a new term starts, so the last one is finished
last = num if op == "+" else -num
elif op == "*":
last *= num
else:
last = int(last / num) # truncate toward zero
op, num = c, 0
return result + last
class Solution {
public int calculate(String s) {
int result = 0; // the sum of the finished terms
int last = 0; // the term still being built
int num = 0;
char op = '+'; // the operator before the number being read
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (Character.isDigit(c)) num = num * 10 + (c - '0');
if ((!Character.isDigit(c) && c != ' ') || i == s.length() - 1) { // an operator, or the end
if (op == '+' || op == '-') {
result += last; // a new term starts, so the last one is finished
last = op == '+' ? num : -num;
} else if (op == '*') {
last *= num;
} else {
last /= num; // truncates toward zero
}
op = c;
num = 0;
}
}
return result + last;
}
}
var calculate = function(s) {
let result = 0; // the sum of the finished terms
let last = 0; // the term still being built
let num = 0;
let op = "+"; // the operator before the number being read
for (let i = 0; i < s.length; i++) {
const c = s[i];
if (c >= "0" && c <= "9") num = num * 10 + Number(c);
if ("+-*/".includes(c) || i === s.length - 1) { // an operator, or the end
if (op === "+" || op === "-") {
result += last; // a new term starts, so the last one is finished
last = op === "+" ? num : -num;
} else if (op === "*") {
last *= num;
} else {
last = Math.trunc(last / num); // truncate toward zero
}
op = c;
num = 0;
}
}
return result + last;
};
Complexity Analysis
Time Complexity: O(n)
One pass over s, with constant work per character.
Space Complexity: O(1)
Four variables, however long s is.
Common Mistakes
Each of these looks right on a quick read, and the tests still reject it. The outputs below come from running the code.
Forgetting the last number. if c in "+-*/":
On s = "3+2*2" it returns 5; the answer is 7. A number is applied only when the operator after it arrives, so the last one is never applied unless the loop also acts at the end of the string.
Dividing with Python's //. terms.append(terms.pop() // num)
On s = "14-3/2" it returns 12; the answer is 13. A subtracted term is pushed as a negative number, and Python's // rounds down: -3 // 2 is -2, but the problem truncates toward zero, -1. Use int(a / b).
Taking a space for an operator. if not c.isdigit() or i == len(s) - 1:
On s = "3 + 5" it crashes with ZeroDivisionError: division by zero; the answer is 8. Testing not c.isdigit() also fires on a space. The space then becomes the pending operator, so what comes next falls through to the last branch, division, here by a num of 0. Act only on +, -, * and /, and at the end.
Where This Pattern Is Taught
This problem practises evaluating an expression with a stack. It's taught in Evaluating Expressions with Stacks, along with the shunting yard, which handles brackets and more levels of precedence.