Files
codetutor/backend/data/questions/basic-calculator-ii.yaml

206 lines
10 KiB
YAML

title: Basic Calculator II
slug: basic-calculator-ii
difficulty: medium
leetcode_id: 227
leetcode_url: https://leetcode.com/problems/basic-calculator-ii/
categories:
- strings
- stack
- math
patterns:
- monotonic-stack
description: |
Given a string `s` which represents an expression, *evaluate this expression and return its value*.
The integer division should truncate toward zero.
You may assume that the given expression is always valid. All intermediate results will be in the range of `[-2^31, 2^31 - 1]`.
**Note:** You are not allowed to use any built-in function which evaluates strings as mathematical expressions, such as `eval()`.
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
examples:
- input: 's = "3+2*2"'
output: "7"
explanation: "Following operator precedence, multiplication is evaluated first: 2*2 = 4, then addition: 3+4 = 7."
- input: 's = " 3/2 "'
output: "1"
explanation: "3 divided by 2 equals 1.5, which truncates toward zero to give 1. Spaces are ignored."
- input: 's = " 3+5 / 2 "'
output: "5"
explanation: "Division has higher precedence: 5/2 = 2 (truncated), then 3+2 = 5."
explanation:
intuition: |
Think of this problem like a human would evaluate the expression: you scan from left to right, but you need to handle **operator precedence** — multiplication and division must be computed before addition and subtraction.
Imagine you're building up a sum of "terms". Each term is either a single number (from `+` or `-` operations) or the result of a multiplication/division chain. When you see `3+2*4`, you can't immediately add 3 and 2 — you need to wait and see what happens to the 2.
The key insight is to use a **stack to defer addition and subtraction**. When we encounter `*` or `/`, we immediately apply them to the most recent number (pop from stack, compute, push result). When we encounter `+` or `-`, we simply push the current number onto the stack (with appropriate sign). At the end, we sum everything on the stack.
Think of it like this: the stack accumulates all the "terms" that need to be added together. Multiplication and division modify the last term before it gets added, while addition and subtraction just push new terms.
approach: |
We solve this using a **Stack-Based Evaluation** approach:
**Step 1: Initialise variables**
- `stack`: An empty list to hold intermediate values (terms to be summed)
- `current_num`: Set to `0` to accumulate digits of multi-digit numbers
- `operation`: Set to `'+'` as the implicit operator before the first number
&nbsp;
**Step 2: Iterate through each character**
- If the character is a digit, accumulate it into `current_num` (handle multi-digit numbers)
- If the character is an operator (`+`, `-`, `*`, `/`) or we've reached the end of the string:
- Apply the **previous** operation to `current_num`:
- `'+'`: Push `current_num` onto the stack
- `'-'`: Push `-current_num` onto the stack
- `'*'`: Pop the top, multiply by `current_num`, push the result
- `'/'`: Pop the top, divide by `current_num` (truncate toward zero), push the result
- Update `operation` to the current operator
- Reset `current_num` to `0`
- Skip spaces entirely
&nbsp;
**Step 3: Sum the stack**
- Return the sum of all values in the stack
- This gives us the final result since all terms have been properly evaluated
&nbsp;
The elegance of this approach is that we handle precedence naturally: `*` and `/` immediately modify the last term on the stack, while `+` and `-` just add new terms to be summed later.
common_pitfalls:
- title: Forgetting Operator Precedence
description: |
Processing operators left-to-right without considering precedence will give wrong answers.
For example, `"3+2*4"` processed left-to-right would compute `(3+2)*4 = 20`, but the correct answer is `3+(2*4) = 11`.
The stack-based approach handles this by immediately applying `*` and `/` to the previous operand, while deferring `+` and `-` operations until the end.
wrong_approach: "Evaluate operators left-to-right as encountered"
correct_approach: "Use a stack to defer addition/subtraction while immediately computing multiplication/division"
- title: Integer Division Truncation Direction
description: |
Python's `//` operator performs floor division, which rounds toward negative infinity. However, this problem requires truncation toward zero.
For example, `-7 // 2 = -4` in Python (floor), but the problem expects `-3` (truncate toward zero).
Use `int(a / b)` instead of `a // b` to get truncation toward zero behaviour.
wrong_approach: "Using `//` for division"
correct_approach: "Using `int(a / b)` for truncation toward zero"
- title: Multi-Digit Numbers
description: |
The expression may contain multi-digit numbers like `"123+456"`. You must accumulate consecutive digits into a single number before applying any operation.
A common mistake is treating each digit as a separate number, which would interpret `"12"` as `1` and `2` separately.
wrong_approach: "Processing each digit independently"
correct_approach: "Accumulate digits: `current_num = current_num * 10 + int(char)`"
- title: Processing the Last Number
description: |
After the loop ends, the last number hasn't been processed yet because there's no trailing operator to trigger processing.
You must either add a dummy operator at the end of the string, or check if you're at the last character inside the loop and process accordingly.
wrong_approach: "Only processing numbers when an operator is encountered"
correct_approach: "Also process when reaching the end of the string"
key_takeaways:
- "**Stack for deferred computation**: Use a stack when you need to delay certain operations (addition/subtraction) while immediately handling others (multiplication/division)"
- "**Operator precedence handling**: Higher precedence operators modify the previous operand; lower precedence operators push new terms"
- "**Track the previous operator**: Process numbers when you see the *next* operator, not the current one — this naturally handles the final number"
- "**Foundation for calculator problems**: This pattern extends to Basic Calculator I (with parentheses) and III (with parentheses and negation)"
time_complexity: "O(n). We traverse the string once, and each character is processed in constant time. Stack operations (push, pop, sum) are all O(1) amortised or O(n) total."
space_complexity: "O(n). In the worst case with all addition operations like `1+2+3+...+n`, the stack holds n/2 numbers."
solutions:
- approach_name: Stack-Based Evaluation
is_optimal: true
code: |
def calculate(s: str) -> int:
stack = []
current_num = 0
operation = '+' # Implicit + before first number
for i, char in enumerate(s):
# Accumulate digits for multi-digit numbers
if char.isdigit():
current_num = current_num * 10 + int(char)
# Process when we hit an operator or end of string
if char in '+-*/' or i == len(s) - 1:
if operation == '+':
stack.append(current_num)
elif operation == '-':
stack.append(-current_num)
elif operation == '*':
# Multiply with the last term
stack.append(stack.pop() * current_num)
elif operation == '/':
# Truncate toward zero (not floor division)
stack.append(int(stack.pop() / current_num))
# Update for next iteration
operation = char
current_num = 0
# Sum all terms on the stack
return sum(stack)
explanation: |
**Time Complexity:** O(n) — Single pass through the string, with O(n) for the final sum.
**Space Complexity:** O(n) — Stack can hold up to n/2 numbers in the worst case.
We use the previous operator to decide what to do with each number. Addition and subtraction push signed values to the stack (deferred computation). Multiplication and division immediately combine with the top of the stack (higher precedence). The final sum gives us the result.
- approach_name: No Stack (Optimised Space)
is_optimal: false
code: |
def calculate(s: str) -> int:
result = 0 # Running total of all completed terms
last_num = 0 # The last term (may still be modified by * or /)
current_num = 0 # Number being built from digits
operation = '+'
for i, char in enumerate(s):
if char.isdigit():
current_num = current_num * 10 + int(char)
if char in '+-*/' or i == len(s) - 1:
if operation == '+':
result += last_num # Finalise previous term
last_num = current_num
elif operation == '-':
result += last_num
last_num = -current_num
elif operation == '*':
last_num *= current_num
elif operation == '/':
last_num = int(last_num / current_num)
operation = char
current_num = 0
return result + last_num # Don't forget the last term
explanation: |
**Time Complexity:** O(n) — Single pass through the string.
**Space Complexity:** O(1) — Only uses a fixed number of variables.
Instead of a stack, we track `result` (sum of completed terms) and `last_num` (the current term that might still be modified by `*` or `/`). When we see `+` or `-`, we know the previous term is complete and add it to `result`. This achieves O(1) space but is slightly harder to reason about.