Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

12 Core Algorithmic Patterns & 168 Practice Problems

  • 1. Two Pointers (9 Paradigms, 32 Problems): Converging pointers, sorted pair sums, container with most water, trapping rain water, 3Sum, plus the Sliding Window and Fast & Slow Pointers paradigms (Floyd cycle detection, monotonic window invariants, longest substrings, minimum window).
  • 2. Binary Search (8 Paradigms, 12 Problems): Monotonic predicate partitioning, boundary searching, rotated arrays, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 8 Problems): Bitmasking, XOR tricks, counting set bits, subset enumeration via bitmasks.
  • 4. Math & Geometry (5 Paradigms, 10 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (8 Paradigms, 17 Problems): Path sums, lowest common ancestor, tree diameter, subtree serialization, validating BSTs.
  • 6. Tree/Graph Breadth-First Search (4 Paradigms, 11 Problems): Level-order traversals, shortest path, rotting oranges, word ladders.
  • 7. Graphs (6 Paradigms, 14 Problems): Topological sort, cycle detection, Dijkstra, bipartite validation, network delay.
  • 8. Backtracking (5 Paradigms, 13 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens.
  • 9. Dynamic Programming (7 Paradigms, 14 Problems): Memoization vs tabulation, knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (8 Paradigms, 10 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (5 Paradigms, 13 Problems): Trie, Union-Find, LRU Cache, LFU Cache, Monotonic Stacks.
  • 12. Intervals & Stack / Miscellaneous (7 Paradigms, 14 Problems): Merge intervals, daily temperatures, largest rectangle in histogram, trapping rain water via stack.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Multi-language Rosetta Stone contrasting abstract invariants with concrete solutions across Python, C#, Java, TypeScript, C++, Go, and Rust.
  2. Stage 2 (Active Recall): Reconstruct algorithmic template invariants from memory with real-time feedback before looking at solutions.
  3. Stage 3 (Senior SWE AI Mock Coach): Simulated senior mock interview evaluating Big-O space/time tradeoffs, edge cases, and code reviews in Monaco Editor.
  4. Stage 4 (Solve on Your Own): Timed sandbox challenges verified against automated test suites in Python, C#, Java, and TypeScript.

Equipped with SM-2 Spaced Repetition Review Hub, Studio Cockpit workspace layout, and interactive study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Compute Coins: 40 free coins upon signup, +20 daily login bonus, +25 referral bonus.
  • 24-Hour AI Coaching Pass: 5 compute coins unlocks unlimited senior SWE AI coaching for a full 24 hours.
  • BYOK (Bring Your Own Key): Completely free unlimited AI coaching if using your own Gemini/OpenAI API key.
  • Refund & Subscription Policy: No recurring charges, no subscription traps, and no paid paywalls. Free tier provides full learning path.
  • Platform Operator: Hi👋WebEnterprise Inc. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

180Items
Theory Context•Miscellaneous & Sweeps
MediumLC 155

Min Stack (LeetCode 155)

You will see how storing one extra number with each entry turns the stack's minimum into a single read.

Target Frequency:AmazonBloombergMicrosoft

Build a stack class, MinStack, that behaves like any last-in, first-out stack but can also report its smallest value at any moment. Each of its operations has to run in O(1) time.

  • MinStack() creates an empty stack.
  • void push(int val) puts val on top.
  • void pop() throws away the value on top.
  • int top() returns the value on top and leaves it in place.
  • int getMin() returns the smallest value among everything currently on the stack.

Worked Examples

Example 1
Input:["MinStack","push","push","push","getMin","pop","top","getMin"] [[],[-2],[0],[-3],[],[],[],[]]
Output:[null,null,null,null,-3,null,0,-2]
(-2, -2)0(0, -2)1(-3, -3)2bottomtop: getMin() = -3
Explanation: Once `-2`, `0` and `-3` are pushed, the smallest value is `-3`. Popping removes `-3`, so `0` is on top again and the smallest value goes back to `-2`.

⚖️Formal Constraints & Bounds

  • -231 <= val <= 231 - 1

  • pop, top and getMin are only called when the stack is not empty.

  • At most 3 * 104 calls are made to push, pop, top and getMin altogether.

Deep-Dive & Conceptual Insights

Why It Works & Core Invariant

A stack only changes at its top, so nothing below an entry changes while the entry is there: store with each entry the minimum from the bottom up to it, and the top entry holds the answer before and after every pop.

Real-World Scenario & Production Applications

An undo history, a call stack in a profiler or a parser's stack often has to report the lowest (or highest, or total) value among everything still on it, right after every push and pop. Rescanning the stack after each change costs time proportional to its size; storing the answer with each entry makes it one read.

Step-by-Step Execution Trace Table

Example 1 (the trap case: the minimum is popped):

Callself.stack after the call (bottom to top)ReturnsWhat happened
MinStack()[]nullAn empty stack
push(-2)[(-2, -2)]nullFirst entry: current_min = val = -2
push(0)[(-2, -2), (0, -2)]nullmin(0, -2) = -2: the minimum carried below wins
push(-3)[(-2, -2), (0, -2), (-3, -3)]nullmin(-3, -2) = -3: a new minimum
getMin()unchanged-3Read self.stack[-1][1]
pop()[(-2, -2), (0, -2)]nullThe new top already carries -2: nothing to recompute
top()unchanged0Read self.stack[-1][0]
getMin()unchanged-2One self.min variable would still say -3 here
Scroll horizontally to see all columns, or expand to full screen
Core Invariant Specification & Code Shape
Archetype Code Shape
Python
1A stack only changes at its top, so what is below an entry never changes while the entry is there: store the answer for the bottom-up part with the entry, as a pair `(val, current_min)`.
2Keep this true: each entry's `current_min` is the minimum of its own `val` and every value below it, so `self.stack[-1][1]` answers `getMin`.
3`push`: `current_min = min(val, self.stack[-1][1])` when the stack has entries, else `val`, then append the pair; `pop` removes the top pair; `top` reads `self.stack[-1][0]`.
4The trap: don't keep one `self.min` for the whole stack. After the smallest value is popped it can't give you the next smallest; the new top's `self.stack[-1][1]` already does.

Target: Min Stack (LeetCode 155). Each entry is `(val, current_min)`: the value and the minimum from the bottom of the stack up to it.

Boundary Model: Sorted Coordinate Sweep-Line & Monotonic Stack

Intervals sorted by start coordinate; monotonic stack preserves strictly increasing or decreasing element values.

Loop Invariant Termination

Sweep: if curr.start <= prev.end: merge; Stack: while stack and nums[i] >= stack[-1]: stack.pop().

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

A plain stack answers top() at once but has to scan everything to find its minimum. One running variable, self.min, fixes push but breaks pop: when the smallest value leaves, the variable can't know the next smallest. The Min-Stack Trick uses the one fact that makes a stack special: it only ever changes at its top. While an entry sits on the stack, nothing below it changes, so the minimum of "this entry and everything below it" stays true until the entry itself is popped. Compute that minimum once, at push time, and store it in the entry as (val, current_min). The top entry then always holds the minimum of the whole stack, and pop needs no repair.

🥞 The Analogy: A Pile of Plates With a Note on Each

You pile up plates of different sizes, and someone keeps asking for the size of the smallest plate in the pile. Each time you put a plate down, you stick a note on it: "the smallest plate from here down is size X", copying the number from the note below if that one is smaller. To answer, you read the note on the top plate. When you lift a plate off, the note on the plate now on top was written when that plate was the top, and nothing under it has changed since, so it is still right.

🪄 The Mathematical Harmony / Magic Trick
Code / Blueprint
if self.stack:
current_min = min(val, self.stack[-1][1])
else:
current_min = val
self.stack.append((val, current_min))
 

current_min is a running minimum from the bottom of the stack up to this entry, and it is stored with the entry, so every prefix of the stack keeps its own answer. Popping removes exactly one prefix, and the prefix below still has its answer on its own top entry. Compare val with the minimum carried below, self.stack[-1][1], not with the value below, self.stack[-1][0].

💡 Summary

Store the answer with each entry: (val, current_min), where current_min = min(val, self.stack[-1][1]). getMin reads the top pair, pop removes it, and nothing is ever recomputed. O(1)O(1)O(1) per call, O(N)O(N)O(N) space.

  • One running minimum for the whole stack: a single self.min is right after every push but not after a pop of the minimum: on LeetCode's example it still says -3 after -3 is popped. Store current_min in each entry, and the new top already knows -2.

  • Comparing with the value below instead of the minimum below: min(val, self.stack[-1][0]) looks at one neighbour only; after push(1), push(5), push(3) it stores 3 as the minimum instead of 1. Use self.stack[-1][1].

  • Reading self.stack[-1] on the first push: an empty stack has no top, so the first entry's current_min is val itself (if self.stack: ... else: current_min = val).

  • Two stacks with a strict <: in the two-stack version, push onto the minimum stack when val <= min_stack[-1]. With <, pushing 0, 1, 0 and popping once throws away the only record of 0 while a 0 is still on the stack.

  • Scanning on getMin: min(v for v, _ in self.stack) is correct but O(N) per call, about 2 * 108 steps at these limits; the carried minimum makes it one read.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

You will see how a senior engineer spots an O(1) aggregate on a stack and defends carrying it with each entry out loud.

Pattern Recognition Signals

The 10-second spot

"Retrieve the minimum element in constant time" on a structure that also pushes and pops: a question about the stack's whole contents (its minimum), answered in O(1) per call while values come and go. A stack only ever changes at its top, which is the signal for the Min-Stack Trick: store the answer with each entry.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

Every entry (val, current_min) in self.stack holds the minimum of its own val and every value below it, so self.stack[-1][1] is the minimum of the whole stack. push sets current_min = min(val, self.stack[-1][1]) (or val on an empty stack); pop only removes the top entry.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • Don't keep the minimum in one self.min variable: after push(-2), push(0), push(-3) and pop(), one variable still says -3 and can't find -2 without a scan; the new top's self.stack[-1][1] already holds -2.

  • min(val, self.stack[-1][1]), not min(val, self.stack[-1][0]): after push(1), push(5), push(3), comparing with the value below stores 3 instead of 1.

  • The first push has no self.stack[-1] to read: its current_min is val itself.

  • Two-stack version: push onto the minimum stack when val <= min_stack[-1], not <, or popping one copy of a repeated minimum removes the only record of the other copy.

The 60-Second Interview Pitch

Say this out loud before you type a single line

I'd use the Min-Stack Trick: store extra state with each entry. A stack only changes at its top, so nothing below an entry changes while that entry is on the stack. That lets me store, with each value, the minimum of itself and everything below it, as a pair: val and current min. On push, current min is the smaller of val and the minimum carried by the entry below, or just val when the stack is empty. getMin reads the second field of the top pair, and pop removes the top pair: the new top already carries the minimum of what is left, so nothing is recomputed. The trap is one running min variable for the whole stack: after the smallest value is popped, it can't tell you the next smallest without a scan. Every call is O(1) time, and the pairs take O(N) space.

So: a stack only changes at its top, so each entry can carry the minimum below it; pop needs no repair, and one self.min is the trap.

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(1) per call

Look at each method. push does one test (if self.stack), at most one min of two numbers and one append; pop removes the last list item; top and getMin each read one field of self.stack[-1]. No method loops over the stack, so every call is O(1) (appending to and popping from the end of a Python list are amortized O(1)), and a run of Q calls costs O(Q).

SPACE COMPLEXITY

O(N)

self.stack holds one (val, current_min) pair for each of the N values on the stack: 2N numbers, so O(N) space. Carrying the minimum doubles the memory of a plain stack; that is the price of an O(1) getMin.

Formal Recurrence Relation

T(Q) = Q · O(1) = O(Q) for Q calls

Look at each method. push does one test (if self.stack), at most one min of two numbers and one append; pop removes the last list item; top and getMin each read one field of self.stack[-1]. No method loops over the stack, so every call is O(1) (appending to and popping from the end of a Python list are amortized O(1)), and a run of Q calls costs O(Q).

Derivation Progression

push

O(1)

One test if self.stack, at most one min of two numbers, one append at the end of the list.

pop

O(1)

self.stack.pop() removes the last pair; nothing is recomputed, because the new top already carries its minimum.

top and getMin

O(1)

Each reads one field of self.stack[-1]: no loop over the stack.

Total

O(Q)

Constant work per call over Q calls.

Variable Definitions

NNN

Number of values on the stack, len(self.stack)

QQQ

Number of calls to push, pop, top and getMin (at most 3 * 104)

Memory Architecture & Bounds

🟣 Call Stack

O(1) No recursion

🔵 Auxiliary Heap

O(N): one (val, current_min) pair per value, twice a plain stack

🟢 Output Space

O(1) per call: one integer or nothing

Boundary Best / Worst Cases

Best Case

O(1)O(1)O(1) per call

Average Case

O(1)O(1)O(1) per call

Worst Case

O(1)O(1)O(1) amortized per call (a Python list occasionally grows its buffer on append)

Recurrence Tree Topology

Recurrence Tree Topology
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Triggers: "retrieve the minimum element in constant time", "push, pop, top". A question about the whole stack (its minimum) that must be answered in O(1)O(1)O(1) while values come and go at the top: Min-Stack Trick, store the answer with each entry.

CONSTRAINTS & BOUNDS

Up to 3⋅1043 \cdot 10^43⋅104 calls and values in [−231,231−1][-2^{31}, 2^{31} - 1][−231,231−1]. Scanning the stack on every getMin costs up to 1.5⋅1041.5 \cdot 10^41.5⋅104 steps per call, about 2⋅1082 \cdot 10^82⋅108 in total; one (val, current_min) pair per entry makes every call O(1)O(1)O(1) for O(N)O(N)O(N) memory, twice a plain stack.

FAANG PRODUCTION TRAPS & EDGE CASES

One shared self.min can't be undone when the minimum is popped. If several threads share the stack, push and pop each pair as one unit: two separate stacks (values and minimums) updated in two steps can drift apart. The carried minimum has the same type as the value, so the −231-2^{31}−231 edge needs no special case.

Core Algorithmic State Invariants

1. Each Entry Carries Its Prefix Minimum

Every entry `(val, current_min)` holds the minimum of its own `val` and every value below it, so `self.stack[-1][1]` is the minimum of the whole stack.

2. Only the Top Changes

A stack only pushes and pops at its top, so nothing below an entry changes while it is there: its `current_min` stays true, and after `pop` the new top already carries the right minimum. One shared `self.min` can't be undone that way.

3. Read, Never Scan

`push` does one `min(val, self.stack[-1][1])` and `getMin` one read, so every call is O(1), for one extra number per entry: O(N) space.

Theory Context•Miscellaneous & Sweeps
MediumLC 155

Min Stack (LeetCode 155)

You will see how storing one extra number with each entry turns the stack's minimum into a single read.

Target Frequency:AmazonBloombergMicrosoft

Build a stack class, MinStack, that behaves like any last-in, first-out stack but can also report its smallest value at any moment. Each of its operations has to run in O(1) time.

  • MinStack() creates an empty stack.
  • void push(int val) puts val on top.
  • void pop() throws away the value on top.
  • int top() returns the value on top and leaves it in place.
  • int getMin() returns the smallest value among everything currently on the stack.

Worked Examples

Example 1
Input:["MinStack","push","push","push","getMin","pop","top","getMin"] [[],[-2],[0],[-3],[],[],[],[]]
Output:[null,null,null,null,-3,null,0,-2]
(-2, -2)0(0, -2)1(-3, -3)2bottomtop: getMin() = -3
Explanation: Once `-2`, `0` and `-3` are pushed, the smallest value is `-3`. Popping removes `-3`, so `0` is on top again and the smallest value goes back to `-2`.

⚖️Formal Constraints & Bounds

  • -231 <= val <= 231 - 1

  • pop, top and getMin are only called when the stack is not empty.

  • At most 3 * 104 calls are made to push, pop, top and getMin altogether.

Deep-Dive & Conceptual Insights

Why It Works & Core Invariant

A stack only changes at its top, so nothing below an entry changes while the entry is there: store with each entry the minimum from the bottom up to it, and the top entry holds the answer before and after every pop.

Real-World Scenario & Production Applications

An undo history, a call stack in a profiler or a parser's stack often has to report the lowest (or highest, or total) value among everything still on it, right after every push and pop. Rescanning the stack after each change costs time proportional to its size; storing the answer with each entry makes it one read.

Step-by-Step Execution Trace Table

Example 1 (the trap case: the minimum is popped):

Callself.stack after the call (bottom to top)ReturnsWhat happened
MinStack()[]nullAn empty stack
push(-2)[(-2, -2)]nullFirst entry: current_min = val = -2
push(0)[(-2, -2), (0, -2)]nullmin(0, -2) = -2: the minimum carried below wins
push(-3)[(-2, -2), (0, -2), (-3, -3)]nullmin(-3, -2) = -3: a new minimum
getMin()unchanged-3Read self.stack[-1][1]
pop()[(-2, -2), (0, -2)]nullThe new top already carries -2: nothing to recompute
top()unchanged0Read self.stack[-1][0]
getMin()unchanged-2One self.min variable would still say -3 here
Scroll horizontally to see all columns, or expand to full screen
Core Invariant Specification & Code Shape
Archetype Code Shape
Python
1A stack only changes at its top, so what is below an entry never changes while the entry is there: store the answer for the bottom-up part with the entry, as a pair `(val, current_min)`.
2Keep this true: each entry's `current_min` is the minimum of its own `val` and every value below it, so `self.stack[-1][1]` answers `getMin`.
3`push`: `current_min = min(val, self.stack[-1][1])` when the stack has entries, else `val`, then append the pair; `pop` removes the top pair; `top` reads `self.stack[-1][0]`.
4The trap: don't keep one `self.min` for the whole stack. After the smallest value is popped it can't give you the next smallest; the new top's `self.stack[-1][1]` already does.

Target: Min Stack (LeetCode 155). Each entry is `(val, current_min)`: the value and the minimum from the bottom of the stack up to it.

Boundary Model: Sorted Coordinate Sweep-Line & Monotonic Stack

Intervals sorted by start coordinate; monotonic stack preserves strictly increasing or decreasing element values.

Loop Invariant Termination

Sweep: if curr.start <= prev.end: merge; Stack: while stack and nums[i] >= stack[-1]: stack.pop().

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

A plain stack answers top() at once but has to scan everything to find its minimum. One running variable, self.min, fixes push but breaks pop: when the smallest value leaves, the variable can't know the next smallest. The Min-Stack Trick uses the one fact that makes a stack special: it only ever changes at its top. While an entry sits on the stack, nothing below it changes, so the minimum of "this entry and everything below it" stays true until the entry itself is popped. Compute that minimum once, at push time, and store it in the entry as (val, current_min). The top entry then always holds the minimum of the whole stack, and pop needs no repair.

🥞 The Analogy: A Pile of Plates With a Note on Each

You pile up plates of different sizes, and someone keeps asking for the size of the smallest plate in the pile. Each time you put a plate down, you stick a note on it: "the smallest plate from here down is size X", copying the number from the note below if that one is smaller. To answer, you read the note on the top plate. When you lift a plate off, the note on the plate now on top was written when that plate was the top, and nothing under it has changed since, so it is still right.

🪄 The Mathematical Harmony / Magic Trick
Code / Blueprint
if self.stack:
current_min = min(val, self.stack[-1][1])
else:
current_min = val
self.stack.append((val, current_min))
 

current_min is a running minimum from the bottom of the stack up to this entry, and it is stored with the entry, so every prefix of the stack keeps its own answer. Popping removes exactly one prefix, and the prefix below still has its answer on its own top entry. Compare val with the minimum carried below, self.stack[-1][1], not with the value below, self.stack[-1][0].

💡 Summary

Store the answer with each entry: (val, current_min), where current_min = min(val, self.stack[-1][1]). getMin reads the top pair, pop removes it, and nothing is ever recomputed. O(1)O(1)O(1) per call, O(N)O(N)O(N) space.

  • One running minimum for the whole stack: a single self.min is right after every push but not after a pop of the minimum: on LeetCode's example it still says -3 after -3 is popped. Store current_min in each entry, and the new top already knows -2.

  • Comparing with the value below instead of the minimum below: min(val, self.stack[-1][0]) looks at one neighbour only; after push(1), push(5), push(3) it stores 3 as the minimum instead of 1. Use self.stack[-1][1].

  • Reading self.stack[-1] on the first push: an empty stack has no top, so the first entry's current_min is val itself (if self.stack: ... else: current_min = val).

  • Two stacks with a strict <: in the two-stack version, push onto the minimum stack when val <= min_stack[-1]. With <, pushing 0, 1, 0 and popping once throws away the only record of 0 while a 0 is still on the stack.

  • Scanning on getMin: min(v for v, _ in self.stack) is correct but O(N) per call, about 2 * 108 steps at these limits; the carried minimum makes it one read.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

You will see how a senior engineer spots an O(1) aggregate on a stack and defends carrying it with each entry out loud.

Pattern Recognition Signals

The 10-second spot

"Retrieve the minimum element in constant time" on a structure that also pushes and pops: a question about the stack's whole contents (its minimum), answered in O(1) per call while values come and go. A stack only ever changes at its top, which is the signal for the Min-Stack Trick: store the answer with each entry.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

Every entry (val, current_min) in self.stack holds the minimum of its own val and every value below it, so self.stack[-1][1] is the minimum of the whole stack. push sets current_min = min(val, self.stack[-1][1]) (or val on an empty stack); pop only removes the top entry.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • Don't keep the minimum in one self.min variable: after push(-2), push(0), push(-3) and pop(), one variable still says -3 and can't find -2 without a scan; the new top's self.stack[-1][1] already holds -2.

  • min(val, self.stack[-1][1]), not min(val, self.stack[-1][0]): after push(1), push(5), push(3), comparing with the value below stores 3 instead of 1.

  • The first push has no self.stack[-1] to read: its current_min is val itself.

  • Two-stack version: push onto the minimum stack when val <= min_stack[-1], not <, or popping one copy of a repeated minimum removes the only record of the other copy.

The 60-Second Interview Pitch

Say this out loud before you type a single line

I'd use the Min-Stack Trick: store extra state with each entry. A stack only changes at its top, so nothing below an entry changes while that entry is on the stack. That lets me store, with each value, the minimum of itself and everything below it, as a pair: val and current min. On push, current min is the smaller of val and the minimum carried by the entry below, or just val when the stack is empty. getMin reads the second field of the top pair, and pop removes the top pair: the new top already carries the minimum of what is left, so nothing is recomputed. The trap is one running min variable for the whole stack: after the smallest value is popped, it can't tell you the next smallest without a scan. Every call is O(1) time, and the pairs take O(N) space.

So: a stack only changes at its top, so each entry can carry the minimum below it; pop needs no repair, and one self.min is the trap.

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(1) per call

Look at each method. push does one test (if self.stack), at most one min of two numbers and one append; pop removes the last list item; top and getMin each read one field of self.stack[-1]. No method loops over the stack, so every call is O(1) (appending to and popping from the end of a Python list are amortized O(1)), and a run of Q calls costs O(Q).

SPACE COMPLEXITY

O(N)

self.stack holds one (val, current_min) pair for each of the N values on the stack: 2N numbers, so O(N) space. Carrying the minimum doubles the memory of a plain stack; that is the price of an O(1) getMin.

Formal Recurrence Relation

T(Q) = Q · O(1) = O(Q) for Q calls

Look at each method. push does one test (if self.stack), at most one min of two numbers and one append; pop removes the last list item; top and getMin each read one field of self.stack[-1]. No method loops over the stack, so every call is O(1) (appending to and popping from the end of a Python list are amortized O(1)), and a run of Q calls costs O(Q).

Derivation Progression

push

O(1)

One test if self.stack, at most one min of two numbers, one append at the end of the list.

pop

O(1)

self.stack.pop() removes the last pair; nothing is recomputed, because the new top already carries its minimum.

top and getMin

O(1)

Each reads one field of self.stack[-1]: no loop over the stack.

Total

O(Q)

Constant work per call over Q calls.

Variable Definitions

NNN

Number of values on the stack, len(self.stack)

QQQ

Number of calls to push, pop, top and getMin (at most 3 * 104)

Memory Architecture & Bounds

🟣 Call Stack

O(1) No recursion

🔵 Auxiliary Heap

O(N): one (val, current_min) pair per value, twice a plain stack

🟢 Output Space

O(1) per call: one integer or nothing

Boundary Best / Worst Cases

Best Case

O(1)O(1)O(1) per call

Average Case

O(1)O(1)O(1) per call

Worst Case

O(1)O(1)O(1) amortized per call (a Python list occasionally grows its buffer on append)

Recurrence Tree Topology

Recurrence Tree Topology
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Triggers: "retrieve the minimum element in constant time", "push, pop, top". A question about the whole stack (its minimum) that must be answered in O(1)O(1)O(1) while values come and go at the top: Min-Stack Trick, store the answer with each entry.

CONSTRAINTS & BOUNDS

Up to 3⋅1043 \cdot 10^43⋅104 calls and values in [−231,231−1][-2^{31}, 2^{31} - 1][−231,231−1]. Scanning the stack on every getMin costs up to 1.5⋅1041.5 \cdot 10^41.5⋅104 steps per call, about 2⋅1082 \cdot 10^82⋅108 in total; one (val, current_min) pair per entry makes every call O(1)O(1)O(1) for O(N)O(N)O(N) memory, twice a plain stack.

FAANG PRODUCTION TRAPS & EDGE CASES

One shared self.min can't be undone when the minimum is popped. If several threads share the stack, push and pop each pair as one unit: two separate stacks (values and minimums) updated in two steps can drift apart. The carried minimum has the same type as the value, so the −231-2^{31}−231 edge needs no special case.

Core Algorithmic State Invariants

1. Each Entry Carries Its Prefix Minimum

Every entry `(val, current_min)` holds the minimum of its own `val` and every value below it, so `self.stack[-1][1]` is the minimum of the whole stack.

2. Only the Top Changes

A stack only pushes and pops at its top, so nothing below an entry changes while it is there: its `current_min` stays true, and after `pop` the new top already carries the right minimum. One shared `self.min` can't be undone that way.

3. Read, Never Scan

`push` does one `min(val, self.stack[-1][1])` and `getMin` one read, so every call is O(1), for one extra number per entry: O(N) space.

Rosetta Dual-Monaco Comparison
Python 3
CANONICAL INVARIANT TEMPLATE
Loading...
CONCRETE: MIN STACK (LEETCODE 155)
T = O(1) per callS = O(N)
Loading...
Pattern Implementation Mapping TableCanonical Invariant ⟷ Concrete Code ⟷ Engineering Rationale
Canonical InvariantConcrete CodeEngineering Rationale
One stack of pairs: each entry carries extra stateself.stack: list[tuple[int, int]] = []Each entry is `(val, current_min)`: the value and the minimum from the bottom of the stack up to it.
Build the carried value from the entry belowcurrent_min = min(val, self.stack[-1][1])The entry below already knows the minimum of everything under it, so one `min` with `val` extends it by one entry. Compare with its carried minimum `[1]`, not its value `[0]`.
The bottom entry has nothing below itcurrent_min = valOn an empty stack there is no `self.stack[-1]` to read: the first entry is its own minimum.
Store the answer with the entryself.stack.append((val, current_min))Nothing below this entry can change while it is on the stack, so its `current_min` stays true until it is popped.
Removing the top needs no repairself.stack.pop()The new top was the top once before, and nothing under it has changed since, so its carried minimum is already right.
Answer the aggregate by reading the top entryreturn self.stack[-1][1]The top entry's `current_min` covers the whole stack: a read, never a scan.
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•