#224HardStack
Basic Calculator
Start dumb: recurse into every parenthesis
The obvious first idea treats parentheses literally: scan left to right, and
whenever you hit a (, find its matching ), cut out that substring, and
recursively evaluate it before continuing. It works, but finding each matching
) re-scans the tail, and slicing out substrings copies the string again — so
a deeply nested expression is O(n²) and allocates on every level.
Find the bottleneck
That recursion keeps re-discovering something the single scan already knows: where it is. Diving into a group only needs you to remember two things — the running total you had so far, and the sign sitting in front of the group — so you can pick up exactly where you left off when the group closes. That’s a tiny, bounded amount of state per level, not a whole substring.
The optimal shape
One left-to-right pass:
- Digit → extend the current
number(numbers can be multi-digit). +/-→ commitsign * numberintoresult, resetnumber, and set the sign for the number coming next.(→ pushresultandsign, then reset toresult = 0,sign = +1.)→ commit the last number, thenresult = result * savedSign + savedResult— the inner value, signed, added back onto the outer total.- After the scan, commit the final pending number.
Each character is handled once — O(n) time, O(depth) stack. In the
player, every ( still waiting for its ) stays highlighted as the open
“window”; a closed pair retires to green.
Watch it think
Run the default nested expression, then the tricky ones: -(2+3) (a minus in
front of a whole group), 2-(1-(0-(2))) (nested unary minus), and 100 + 1
(a multi-digit number built one digit at a time).
Re-traces as you type — digits with + - ( ) and spaces — up to 32 chars; try -(2+3), 2-(1-(0-(2))), or 100 + 1. Capped at 32 characters so the tiles stay readable.
Step 1 of 21: Nothing is added yet, so the total is 0 with a pending + sign; the stack — the only place an interrupted sub-total can wait — starts empty. Variables: result = 0, sign = +1, number = 0, stack = ∅.
Data
Code
1fun calculate(s: String): Int {2 val stack = ArrayDeque<Int>()3 var result = 04 var number = 05 var sign = 16 for (c in s) {7 when {8 c.isDigit() -> number = number * 10 + (c - '0')9 c == '+' -> { result += sign * number; number = 0; sign = 1 }10 c == '-' -> { result += sign * number; number = 0; sign = -1 }11 c == '(' -> { stack.addLast(result); stack.addLast(sign); result = 0; sign = 1 }12 c == ')' -> {13 result += sign * number; number = 014 result *= stack.removeLast()15 result += stack.removeLast()16 }17 }18 }19 return result + sign * number20}
State
- result
- 0
- sign
- +1
- number
- 0
- stack
- ∅
Why this step
Nothing is added yet, so the total is 0 with a pending + sign; the stack — the only place an interrupted sub-total can wait — starts empty.
1 / 21
The same pattern also solves
Recognize the window, and these fall to the same skeleton.
- Basic Calculator II (LC 227) ↗
Adds * and / with precedence but drops parentheses — a stack of terms instead of a stack of contexts.
- Basic Calculator III (LC 772) ↗
The union of both — parentheses AND * / together, combining the context stack with immediate precedence.
- Valid Parentheses (LC 20) ↗
The same push-on-'(' discipline with no arithmetic — just checking that every opener finds its closer.
- Decode String (LC 394) ↗
The same save-context-on-'(' / restore-on-')' pattern, applied to nested string repetition instead of sums.