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 & 195 Practice Problems

  • 1. Two Pointers (11 Paradigms, 35 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, 8 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 (10 Paradigms, 19 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (4 Paradigms, 11 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (10 Paradigms, 18 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 (11 Paradigms, 18 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 (6 Paradigms, 15 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Intervals & Stack / Miscellaneous (11 Paradigms, 18 Problems): Merge intervals, daily temperatures, largest rectangle in histogram, trapping rain water via stack.

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

207Items
Theory Context•Advanced Data Structures
MediumLC 729

My Calendar I (LeetCode 729)

You will see how keeping booked events sorted by start means a new event only has to be checked against the two events next to it.

Build a calendar, MyCalendar, that takes events one at a time and keeps only those that fit.

An event is a half-open range of time [startTime, endTime): it covers every moment x with startTime <= x < endTime. Two events double book when some moment belongs to both. So an event that ends at 20 and one that starts at 20 share no moment and do not double book.

  • MyCalendar() creates an empty calendar.
  • boolean book(int startTime, int endTime) adds the event and returns true when it double books with no event already in the calendar. Otherwise it returns false and the calendar stays as it was.

Worked Examples

Example 1
Input:["MyCalendar", "book", "book", "book"] [[], [10, 20], [15, 25], [20, 30]]
Output:[null, true, false, true]
Explanation: `[10, 20)` is stored. `[15, 25)` shares moments 15 to 19 with it, so it is rejected. `[20, 30)` starts exactly where `[10, 20)` stops, so they share no moment and it is stored.

⚖️Formal Constraints & Bounds

  • 0 <= start < end <= 109

  • At most 1000 calls will be made to book.

Deep-Dive & Conceptual Insights

Why It Works & Core Invariant

Stored events never overlap each other, so once they are sorted by start their ends are sorted too. A new event can then only clash with the stored event that starts just before it (the floor) or the one that starts just after it (the ceiling): every other stored event is further away than one of those two.

Real-World Scenario & Production Applications

A meeting-room or equipment booking system accepts a reservation only if the room is free for the whole slot. Reservations arrive in any order, so the system keeps them sorted by start time and compares a new one with the reservation just before it and the one just after it, never with the whole schedule.

Step-by-Step Execution Trace Table

LeetCode's calls book(10, 20), book(15, 25), book(20, 30):

Callstarts / ends beforei = bisect_right(starts, startTime)Floor check ends[i - 1] > startTimeCeiling check starts[i] < endTimeReturns
book(10, 20)[] / []0no floorno ceilingtrue, insert at 0
book(15, 25)[10] / [20]120 > 15: clashfalse
book(20, 30)[10] / [20]120 > 20 is falseno ceilingtrue, insert at 1
Scroll horizontally to see all columns, or expand to full screen

The third call is the trap: the floor event ends exactly at 20, where the new one starts. The check is >, not >=, because the events are half-open and share no moment.

Core Invariant Specification & Code Shape
Archetype Code Shape
Python
1Stored events never overlap, so kept sorted by start they are sorted by end too, and a new event can only clash with its two neighbours.
2Keep this true: `starts` holds every stored start in sorted order, `ends[j]` belongs to `starts[j]`, and no two stored events overlap.
3`book`: `i = bisect.bisect_right(self.starts, startTime)`; reject if the floor `self.ends[i - 1] > startTime` or the ceiling `self.starts[i] < endTime`; else insert at `i` in both lists.
4The trap: events are half-open, so the checks are strict. `>=` or `<=` rejects `book(20, 30)` after `book(10, 20)`, which only touches it.

Target: My Calendar I (LeetCode 729). `starts[j]` and `ends[j]` describe one stored event. Keeping `starts` sorted is what makes a binary search for the neighbours possible.

Boundary Model: Trie Prefix Tree Branching / DSU Near-Flat Forest

Trie: root-to-node path encodes common prefix; DSU: find(u) with path compression flattens tree so root is direct parent.

Loop Invariant Termination

Trie: traverse word char-by-char in O(L); DSU: if find(u) != find(v): union(u, v) in O(alpha(N)).

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

Comparing a new event with every stored event is O(N) per call. But the stored events never overlap each other, so once they are kept sorted by start their ends are sorted too, and a new event can only clash with two of them: the floor, the last stored event that starts at or before the new start, and the ceiling, the first one that starts after it. Every other stored event either ended before the floor started or starts after the ceiling. This is the Ordered Set technique: keep keys sorted while inserting anywhere, and answer "the closest key at or below x" and "the closest key above x" with one binary search.

📖 The Analogy: A Row of Booked Slots on a Timeline

Picture the day's bookings as blocks laid along a ruler, never overlapping. To see whether a new block fits, you put your finger where it would start and look only at the block to the left of your finger and the block to the right. If the left block ends before your start and the right block begins after your end, the new block fits; nothing further away can be in the way.

🪄 The Mathematical Harmony / Magic Trick
Code / Blueprint
i = bisect.bisect_right(self.starts, startTime)
if i > 0 and self.ends[i - 1] > startTime:
return False
if i < len(self.starts) and self.starts[i] < endTime:
return False
 

bisect_right splits the stored events into those that start at or before startTime (indexes below i) and those that start after it. Events are half-open, so the comparisons are strict: an event ending at 20 and one starting at 20 touch but do not overlap.

💡 Summary

One bisect_right finds both neighbours in O(log N); two strict comparisons decide; the event is inserted at i in both lists. A Python list makes that insert O(N); a balanced tree makes it O(log N). O(N) total space.

  • >= instead of > on the floor (or <= on the ceiling): events are half-open, so an event ending at 20 and one starting at 20 do not overlap. A non-strict check rejects book(20, 30) after book(10, 20).

  • Checking only one neighbour: the floor check misses a stored event that starts inside the new one, and the ceiling check misses one that is still running when the new one starts. Both are needed.

  • Storing a rejected event: insert only after both checks pass; a rejected event left in the lists blocks later events that should fit.

  • Lists out of step: insert into starts and ends at the same i; appending to one and inserting into the other pairs the wrong ends with the wrong starts.

  • Re-sorting on every call: starts.sort() after an append works but costs O(N log N) per call; bisect_right plus insert keeps the order without sorting.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

You will see how a senior engineer hears "accept only if it does not overlap" with events arriving in any order and reaches for a sorted structure with floor and ceiling lookups.

Pattern Recognition Signals

The 10-second spot

"book" events one at a time, accepted only if they do not "double book" with any stored event, and the events arrive in any order: each new event needs the closest stored event at or below its start and the closest one above it. That is a floor and a ceiling lookup on a set that keeps growing: Ordered Set.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

starts holds the start of every stored event in sorted order and ends[j] belongs to starts[j]; stored events never overlap, so ends is sorted too. With i = bisect.bisect_right(self.starts, startTime), the new event fits exactly when not (i > 0 and self.ends[i - 1] > startTime) and not (i < len(self.starts) and self.starts[i] < endTime).

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • Half-open events: the floor clashes only when self.ends[i - 1] > startTime, and the ceiling only when self.starts[i] < endTime. With >= or <=, book(20, 30) after book(10, 20) is wrongly rejected.

  • Check both neighbours: the floor alone misses a stored event that starts inside the new one (book(5, 11) after book(10, 20)), and the ceiling alone misses one that is still running when the new one starts.

  • Insert only after both checks pass, and insert into both lists at the same i, so a rejected event is never stored and ends[j] keeps belonging to starts[j].

The 60-Second Interview Pitch

Say this out loud before you type a single line

"I'd use an Ordered Set. I keep the stored events sorted by start, in two lists, starts and ends. For a new event I find i = bisect_right(starts, startTime): the event at i - 1 is the floor, the last one that starts at or before mine, and the event at i is the ceiling, the first one that starts after. Stored events never overlap, so their ends are sorted too, and only those two neighbours can clash: everything earlier ends before the floor starts, everything later starts after the ceiling. The trap is that events are half-open, so the floor clashes only if its end is greater than my start, and the ceiling only if it starts before my end; touching events are fine. If both checks pass I insert at i. Finding the neighbours is O(log N); inserting into a Python list is O(N), and a balanced tree would make it O(log N). Space is O(N)."

So: sorted starts, i = bisect_right(starts, startTime), check the floor ends[i - 1] > startTime and the ceiling starts[i] < endTime, then insert both at i.

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(log N) search + O(N) insert per book

Look at the code: bisect.bisect_right(self.starts, startTime) is a binary search over the N stored starts, O(log N). The floor and ceiling checks read self.ends[i - 1] and self.starts[i], O(1) each. When the event fits, self.starts.insert(i, startTime) and self.ends.insert(i, endTime) shift every entry after i one place to the right, O(N) in the worst case. So each book is O(log N) to search plus O(N) to insert.

SPACE COMPLEXITY

O(N)

Look at memory allocations: starts and ends hold one entry each per stored event, O(N) for N stored events. book makes no recursive calls and keeps only i, so each call uses O(1) extra space.

Formal Recurrence Relation

book = O(log N) search + O(N) insert, N = events stored so far

Derivation Progression

Find the neighbours

i = bisect.bisect_right(self.starts, startTime) ⟹ O(log N)

A binary search over the sorted starts: it halves the range each step.

Floor and ceiling checks

self.ends[i - 1] > startTime, self.starts[i] < endTime ⟹ O(1)

Two index reads and two comparisons, however many events are stored.

Insert in place

self.starts.insert(i, startTime) ⟹ O(N)

A Python list shifts every entry after i; a balanced tree would insert in O(log N).

Total per book

O(log N) + O(1) + O(N) = O(log N) search + O(N) insert

The search is the part the Ordered Set speeds up; the insert cost depends on the container.

Variable Definitions

NNN

Number of events stored so far (at most 1000 book calls)

Memory Architecture & Bounds

🟣 Call Stack

O(1): book is not recursive

🔵 Auxiliary Heap

O(N): starts and ends, one entry each per stored event

🟢 Output Space

O(1) per call: one boolean

Boundary Best / Worst Cases

Best Case

O(log⁡N)O(\log N)O(logN): the event clashes, so nothing is inserted

Average Case

O(log⁡N)O(\log N)O(logN) search + O(N)O(N)O(N) insert

Worst Case

O(N)O(N)O(N): the event fits at the front, so the insert shifts every stored entry

Pointer Invariant Transition Progression

Pointer Invariant Transition Progression
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Triggers: "add a new event if it does not cause a double booking", "half-open interval [startTime, endTime)". Events arrive in any order and each one is judged against the stored set, so the store must stay sorted under inserts in the middle: Ordered Set, with a floor and a ceiling lookup per call.

CONSTRAINTS & BOUNDS

At most 100010001000 calls and times up to 10910^9109. A scan of every stored event is O(N)O(N)O(N) per call, about 5×1055 \times 10^55×105 comparisons in all, which also passes; the ordered set cuts the search to O(log⁡N)O(\log N)O(logN) and is the shape that still works when the calendar holds millions of events.

FAANG PRODUCTION TRAPS & EDGE CASES

Memory and write cost at scale: a sorted array shifts up to NNN entries on every insert, so a store with millions of bookings needs a balanced tree or a B-tree index instead. Concurrency: two requests for the same slot can both pass the neighbour check before either inserts, so the check and the insert must happen under one lock or in one database transaction.

Core Algorithmic State Invariants

  1. Sorted by Start Means Sorted by End

Stored events never overlap, so the one that starts first also ends first: starts and ends are both sorted. That is why the floor event (i - 1) and the ceiling event (i) are the only ones that can clash with a new event.

  1. Floor and Ceiling from One bisect_right

i = bisect_right(starts, startTime) counts the stored events that start at or before startTime: the floor is at i - 1 and the ceiling at i. One binary search gives both neighbours.

  1. O(log N) Search, O(N) List Insert

The search is O(log N), but list.insert shifts every later entry, O(N). A balanced tree (Java TreeMap, C++ std::map, sortedcontainers.SortedList) keeps the same floor and ceiling logic with an O(log N) insert.

Theory Context•Advanced Data Structures
MediumLC 729

My Calendar I (LeetCode 729)

You will see how keeping booked events sorted by start means a new event only has to be checked against the two events next to it.

Build a calendar, MyCalendar, that takes events one at a time and keeps only those that fit.

An event is a half-open range of time [startTime, endTime): it covers every moment x with startTime <= x < endTime. Two events double book when some moment belongs to both. So an event that ends at 20 and one that starts at 20 share no moment and do not double book.

  • MyCalendar() creates an empty calendar.
  • boolean book(int startTime, int endTime) adds the event and returns true when it double books with no event already in the calendar. Otherwise it returns false and the calendar stays as it was.

Worked Examples

Example 1
Input:["MyCalendar", "book", "book", "book"] [[], [10, 20], [15, 25], [20, 30]]
Output:[null, true, false, true]
Explanation: `[10, 20)` is stored. `[15, 25)` shares moments 15 to 19 with it, so it is rejected. `[20, 30)` starts exactly where `[10, 20)` stops, so they share no moment and it is stored.

⚖️Formal Constraints & Bounds

  • 0 <= start < end <= 109

  • At most 1000 calls will be made to book.

Deep-Dive & Conceptual Insights

Why It Works & Core Invariant

Stored events never overlap each other, so once they are sorted by start their ends are sorted too. A new event can then only clash with the stored event that starts just before it (the floor) or the one that starts just after it (the ceiling): every other stored event is further away than one of those two.

Real-World Scenario & Production Applications

A meeting-room or equipment booking system accepts a reservation only if the room is free for the whole slot. Reservations arrive in any order, so the system keeps them sorted by start time and compares a new one with the reservation just before it and the one just after it, never with the whole schedule.

Step-by-Step Execution Trace Table

LeetCode's calls book(10, 20), book(15, 25), book(20, 30):

Callstarts / ends beforei = bisect_right(starts, startTime)Floor check ends[i - 1] > startTimeCeiling check starts[i] < endTimeReturns
book(10, 20)[] / []0no floorno ceilingtrue, insert at 0
book(15, 25)[10] / [20]120 > 15: clashfalse
book(20, 30)[10] / [20]120 > 20 is falseno ceilingtrue, insert at 1
Scroll horizontally to see all columns, or expand to full screen

The third call is the trap: the floor event ends exactly at 20, where the new one starts. The check is >, not >=, because the events are half-open and share no moment.

Core Invariant Specification & Code Shape
Archetype Code Shape
Python
1Stored events never overlap, so kept sorted by start they are sorted by end too, and a new event can only clash with its two neighbours.
2Keep this true: `starts` holds every stored start in sorted order, `ends[j]` belongs to `starts[j]`, and no two stored events overlap.
3`book`: `i = bisect.bisect_right(self.starts, startTime)`; reject if the floor `self.ends[i - 1] > startTime` or the ceiling `self.starts[i] < endTime`; else insert at `i` in both lists.
4The trap: events are half-open, so the checks are strict. `>=` or `<=` rejects `book(20, 30)` after `book(10, 20)`, which only touches it.

Target: My Calendar I (LeetCode 729). `starts[j]` and `ends[j]` describe one stored event. Keeping `starts` sorted is what makes a binary search for the neighbours possible.

Boundary Model: Trie Prefix Tree Branching / DSU Near-Flat Forest

Trie: root-to-node path encodes common prefix; DSU: find(u) with path compression flattens tree so root is direct parent.

Loop Invariant Termination

Trie: traverse word char-by-char in O(L); DSU: if find(u) != find(v): union(u, v) in O(alpha(N)).

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

Comparing a new event with every stored event is O(N) per call. But the stored events never overlap each other, so once they are kept sorted by start their ends are sorted too, and a new event can only clash with two of them: the floor, the last stored event that starts at or before the new start, and the ceiling, the first one that starts after it. Every other stored event either ended before the floor started or starts after the ceiling. This is the Ordered Set technique: keep keys sorted while inserting anywhere, and answer "the closest key at or below x" and "the closest key above x" with one binary search.

📖 The Analogy: A Row of Booked Slots on a Timeline

Picture the day's bookings as blocks laid along a ruler, never overlapping. To see whether a new block fits, you put your finger where it would start and look only at the block to the left of your finger and the block to the right. If the left block ends before your start and the right block begins after your end, the new block fits; nothing further away can be in the way.

🪄 The Mathematical Harmony / Magic Trick
Code / Blueprint
i = bisect.bisect_right(self.starts, startTime)
if i > 0 and self.ends[i - 1] > startTime:
return False
if i < len(self.starts) and self.starts[i] < endTime:
return False
 

bisect_right splits the stored events into those that start at or before startTime (indexes below i) and those that start after it. Events are half-open, so the comparisons are strict: an event ending at 20 and one starting at 20 touch but do not overlap.

💡 Summary

One bisect_right finds both neighbours in O(log N); two strict comparisons decide; the event is inserted at i in both lists. A Python list makes that insert O(N); a balanced tree makes it O(log N). O(N) total space.

  • >= instead of > on the floor (or <= on the ceiling): events are half-open, so an event ending at 20 and one starting at 20 do not overlap. A non-strict check rejects book(20, 30) after book(10, 20).

  • Checking only one neighbour: the floor check misses a stored event that starts inside the new one, and the ceiling check misses one that is still running when the new one starts. Both are needed.

  • Storing a rejected event: insert only after both checks pass; a rejected event left in the lists blocks later events that should fit.

  • Lists out of step: insert into starts and ends at the same i; appending to one and inserting into the other pairs the wrong ends with the wrong starts.

  • Re-sorting on every call: starts.sort() after an append works but costs O(N log N) per call; bisect_right plus insert keeps the order without sorting.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

You will see how a senior engineer hears "accept only if it does not overlap" with events arriving in any order and reaches for a sorted structure with floor and ceiling lookups.

Pattern Recognition Signals

The 10-second spot

"book" events one at a time, accepted only if they do not "double book" with any stored event, and the events arrive in any order: each new event needs the closest stored event at or below its start and the closest one above it. That is a floor and a ceiling lookup on a set that keeps growing: Ordered Set.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

starts holds the start of every stored event in sorted order and ends[j] belongs to starts[j]; stored events never overlap, so ends is sorted too. With i = bisect.bisect_right(self.starts, startTime), the new event fits exactly when not (i > 0 and self.ends[i - 1] > startTime) and not (i < len(self.starts) and self.starts[i] < endTime).

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • Half-open events: the floor clashes only when self.ends[i - 1] > startTime, and the ceiling only when self.starts[i] < endTime. With >= or <=, book(20, 30) after book(10, 20) is wrongly rejected.

  • Check both neighbours: the floor alone misses a stored event that starts inside the new one (book(5, 11) after book(10, 20)), and the ceiling alone misses one that is still running when the new one starts.

  • Insert only after both checks pass, and insert into both lists at the same i, so a rejected event is never stored and ends[j] keeps belonging to starts[j].

The 60-Second Interview Pitch

Say this out loud before you type a single line

"I'd use an Ordered Set. I keep the stored events sorted by start, in two lists, starts and ends. For a new event I find i = bisect_right(starts, startTime): the event at i - 1 is the floor, the last one that starts at or before mine, and the event at i is the ceiling, the first one that starts after. Stored events never overlap, so their ends are sorted too, and only those two neighbours can clash: everything earlier ends before the floor starts, everything later starts after the ceiling. The trap is that events are half-open, so the floor clashes only if its end is greater than my start, and the ceiling only if it starts before my end; touching events are fine. If both checks pass I insert at i. Finding the neighbours is O(log N); inserting into a Python list is O(N), and a balanced tree would make it O(log N). Space is O(N)."

So: sorted starts, i = bisect_right(starts, startTime), check the floor ends[i - 1] > startTime and the ceiling starts[i] < endTime, then insert both at i.

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(log N) search + O(N) insert per book

Look at the code: bisect.bisect_right(self.starts, startTime) is a binary search over the N stored starts, O(log N). The floor and ceiling checks read self.ends[i - 1] and self.starts[i], O(1) each. When the event fits, self.starts.insert(i, startTime) and self.ends.insert(i, endTime) shift every entry after i one place to the right, O(N) in the worst case. So each book is O(log N) to search plus O(N) to insert.

SPACE COMPLEXITY

O(N)

Look at memory allocations: starts and ends hold one entry each per stored event, O(N) for N stored events. book makes no recursive calls and keeps only i, so each call uses O(1) extra space.

Formal Recurrence Relation

book = O(log N) search + O(N) insert, N = events stored so far

Derivation Progression

Find the neighbours

i = bisect.bisect_right(self.starts, startTime) ⟹ O(log N)

A binary search over the sorted starts: it halves the range each step.

Floor and ceiling checks

self.ends[i - 1] > startTime, self.starts[i] < endTime ⟹ O(1)

Two index reads and two comparisons, however many events are stored.

Insert in place

self.starts.insert(i, startTime) ⟹ O(N)

A Python list shifts every entry after i; a balanced tree would insert in O(log N).

Total per book

O(log N) + O(1) + O(N) = O(log N) search + O(N) insert

The search is the part the Ordered Set speeds up; the insert cost depends on the container.

Variable Definitions

NNN

Number of events stored so far (at most 1000 book calls)

Memory Architecture & Bounds

🟣 Call Stack

O(1): book is not recursive

🔵 Auxiliary Heap

O(N): starts and ends, one entry each per stored event

🟢 Output Space

O(1) per call: one boolean

Boundary Best / Worst Cases

Best Case

O(log⁡N)O(\log N)O(logN): the event clashes, so nothing is inserted

Average Case

O(log⁡N)O(\log N)O(logN) search + O(N)O(N)O(N) insert

Worst Case

O(N)O(N)O(N): the event fits at the front, so the insert shifts every stored entry

Pointer Invariant Transition Progression

Pointer Invariant Transition Progression
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Triggers: "add a new event if it does not cause a double booking", "half-open interval [startTime, endTime)". Events arrive in any order and each one is judged against the stored set, so the store must stay sorted under inserts in the middle: Ordered Set, with a floor and a ceiling lookup per call.

CONSTRAINTS & BOUNDS

At most 100010001000 calls and times up to 10910^9109. A scan of every stored event is O(N)O(N)O(N) per call, about 5×1055 \times 10^55×105 comparisons in all, which also passes; the ordered set cuts the search to O(log⁡N)O(\log N)O(logN) and is the shape that still works when the calendar holds millions of events.

FAANG PRODUCTION TRAPS & EDGE CASES

Memory and write cost at scale: a sorted array shifts up to NNN entries on every insert, so a store with millions of bookings needs a balanced tree or a B-tree index instead. Concurrency: two requests for the same slot can both pass the neighbour check before either inserts, so the check and the insert must happen under one lock or in one database transaction.

Core Algorithmic State Invariants

  1. Sorted by Start Means Sorted by End

Stored events never overlap, so the one that starts first also ends first: starts and ends are both sorted. That is why the floor event (i - 1) and the ceiling event (i) are the only ones that can clash with a new event.

  1. Floor and Ceiling from One bisect_right

i = bisect_right(starts, startTime) counts the stored events that start at or before startTime: the floor is at i - 1 and the ceiling at i. One binary search gives both neighbours.

  1. O(log N) Search, O(N) List Insert

The search is O(log N), but list.insert shifts every later entry, O(N). A balanced tree (Java TreeMap, C++ std::map, sortedcontainers.SortedList) keeps the same floor and ceiling logic with an O(log N) insert.

Rosetta Dual-Monaco Comparison
Python 3
CANONICAL INVARIANT TEMPLATE
Loading...
CONCRETE: MY CALENDAR I (LEETCODE 729)
T = O(log N) search + O(N) insert per bookS = O(N)
Loading...
Pattern Implementation Mapping TableCanonical Invariant ⟷ Concrete Code ⟷ Engineering Rationale
Canonical InvariantConcrete CodeEngineering Rationale
Two parallel lists, sorted by startself.starts: list[int] = [] self.ends: list[int] = []`starts[j]` and `ends[j]` describe one stored event. Keeping `starts` sorted is what makes a binary search for the neighbours possible.
Find the floor and the ceilingi = bisect.bisect_right(self.starts, startTime)`i` counts the stored events that start at or before `startTime`: the floor is at `i - 1`, the ceiling at `i`.
Floor check (half-open)if i > 0 and self.ends[i - 1] > startTime: return FalseThe floor clashes only if it is still running at `startTime`. An end equal to `startTime` is fine: the events only touch.
Ceiling check (half-open)if i < len(self.starts) and self.starts[i] < endTime: return FalseThe ceiling clashes only if it starts before `endTime`. Every later event starts even later, so it cannot clash if the ceiling does not.
Insert in sorted placeself.starts.insert(i, startTime) self.ends.insert(i, endTime)Both lists get the new event at the same `i`, so they stay sorted and stay paired.
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•