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).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Stack
MediumLC 155

Min Stack (LeetCode 155)

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

Core Insight

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
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.

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.

🔑 Why It Works

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].

  • 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

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

Decision rule: push stores current_min = min(val, self.stack[-1][1]) (or val on an empty stack); pop only removes the top pair; getMin reads self.stack[-1][1].

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

This problem's traps are in Common Pitfalls above.

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.

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

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

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. Repeated Minimums Are Safe

Pushing -2 twice stores current_min = -2 in both pairs, so popping one still leaves -2 as the minimum. The pair form never needs the <= care that the two-stack version does.

  1. Trading Memory for Simplicity

The pairs cost one extra number per entry. A second stack that records a minimum only when it changes saves memory on long runs of larger values, at the price of the <= rule and two structures to keep in step.

Theory Context•Stack
MediumLC 155

Min Stack (LeetCode 155)

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

Core Insight

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
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.

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.

🔑 Why It Works

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].

  • 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

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

Decision rule: push stores current_min = min(val, self.stack[-1][1]) (or val on an empty stack); pop only removes the top pair; getMin reads self.stack[-1][1].

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

This problem's traps are in Common Pitfalls above.

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.

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

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

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. Repeated Minimums Are Safe

Pushing -2 twice stores current_min = -2 in both pairs, so popping one still leaves -2 as the minimum. The pair form never needs the <= care that the two-stack version does.

  1. Trading Memory for Simplicity

The pairs cost one extra number per entry. A second stack that records a minimum only when it changes saves memory on long runs of larger values, at the price of the <= rule and two structures to keep in step.

© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•