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•Priority Queue / Heap
HardLC 630

Course Schedule III (LeetCode 630)

You will see how taking every course and undoing the longest one when a deadline breaks finds the largest schedule in O(N log N).

Target Frequency:GoogleAmazonMicrosoft

You are offered n online courses. courses[i] = [duration_i, lastDay_i]: course i takes duration_i days in a row, and it has to be finished no later than day lastDay_i. You start on day 1 and can only follow one course at a time, so the courses you choose run back to back.

Return the largest number of courses you can complete.

Worked Examples

Example 1
Input:courses = [[100,200],[200,1300],[1000,1250],[2000,3200]]
Output:3
[100,200][1000,1250][200,1300]11300
Explanation: Take the 100-day course (done on day 100), then the 1000-day course (done on day 1100), then the 200-day course (done on day 1300). The 2000-day course would end on day 3300, after its last day 3200.
Example 2
Input:courses = [[1,2]]
Output:1
Explanation: The only course ends on day 1, before its last day 2.
Example 3
Input:courses = [[3,2],[4,3]]
Output:0
Explanation: Each course is longer than the days available before its last day, so none can be finished.

⚖️Formal Constraints & Bounds

  • 1 <= courses.length <= 104

  • 1 <= duration_i, lastDay_i <= 104

Deep-Dive & Conceptual Insights

Why It Works & Core Invariant

Go through the courses by last day and take each one; when the total runs past the current last day, dropping the longest course taken keeps the same count and frees the most days for what comes next.

Real-World Scenario & Production Applications

Admitting jobs into a queue where each job has a length and a due time: a scheduler that accepts every job, and evicts the longest accepted one whenever a due time would be missed, keeps the number of on-time jobs as high as possible. The same rule appears in classic single-machine scheduling (maximize the number of on-time jobs).

Subproblems & Recurrence Decomposition3 Phases

🧩Subproblem 1: Adjacency List & In-Degree Initialization

Construct directed graph from edge pairs and compute in-degree for every vertex: in_degree[v] = number of prerequisite dependencies.

Mathematical Recurrence / Code Invariant
adj = defaultdict(list)
in_degree = [0] * numCourses
for dest, src in prerequisites:
    adj[src].append(dest)
    in_degree[dest] += 1

Step-by-Step Execution Trace Table

Input courses = [[100,200],[200,1300],[1000,1250],[2000,3200]], sorted by last day: [100,200], [1000,1250], [200,1300], [2000,3200].

StepCourse [duration, last_day]total_time after taking ittotal_time > last_day?Actionmax_heap (durations taken)
1[100, 200]100NoKeep{100}
2[1000, 1250]1100NoKeep{1000, 100}
3[200, 1300]1300No (equal is fine)Keep{1000, 200, 100}
4[2000, 3200]3300YesPop the longest, 2000: total_time = 1300{1000, 200, 100}
Endreturn len(max_heap) = 3
Scroll horizontally to see all columns, or expand to full screen
Core Invariant Specification & Code Shape
Archetype Code Shape
Python
1Take every course, and when the days run out, undo the single worst choice made so far: the longest course taken.
2`max_heap` always holds the most courses that fit the deadlines seen so far, with the smallest possible `total_time`.
3Sort `courses` by last day; for each `duration, last_day`: add `duration` to `total_time`, push `-duration`; if `total_time > last_day`, pop the longest and subtract it; `return len(max_heap)`.
4The trap: sort by `last_day`, not by `duration`, or a short course with a late deadline pushes out a course whose early deadline was still reachable.

Target: Course Schedule III (LeetCode 630). Checking deadlines in increasing order means that when a course fits its own last day, every earlier course (with an earlier last day) fits too.

Boundary Model: Complete Binary Tree / Min-Max Heap Invariant

Heap maintains extreme element at root heap[0]. heappushpop maintains size bounded to K in O(log K) time.

Loop Invariant Termination

heapq.heappush(h, val); if len(h) > k: heapq.heappop(h) — peek min/max in O(1).

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

Some greedy problems can't be solved by picking the right item up front, because you only learn later that an earlier pick was a mistake. Greedy with Regret takes every item as it comes, keeps the picks in a heap, and the moment a limit is broken it undoes the single worst pick. For Course Schedule III: go through courses by deadline, take each one, and if the running total of days now misses the current deadline, drop the longest course taken so far.

🏟️ The Analogy: Packing a Suitcase Against a Weight Limit

You pack items in the order you need them, and at each checkpoint the bag must be under the limit. Whenever you are over, you don't unpack everything and start again: you pull out the single heaviest item. The count of items stays as high as it can be, and the bag is as light as it can be, which leaves the most room for what comes next.

🪄 The Mathematical Harmony / Magic Trick
Code / Blueprint
courses.sort(key=lambda c: c[1]) # by last day
for duration, last_day in courses:
total_time += duration
heapq.heappush(max_heap, -duration) # take it first
if total_time > last_day:
total_time -= -heapq.heappop(max_heap) # regret the longest
return len(max_heap)
 

After each course, max_heap holds the most courses that fit the deadlines seen so far, and among all such sets the one with the smallest total_time. Dropping the longest course keeps the count and frees the most days, so no later course is ever blocked by a choice we could have made better.

💡 Summary

Order by the constraint (the deadline), take greedily, and let a max-heap undo the worst choice when the constraint breaks. Sorting costs O(Nlog⁡N)O(N \log N)O(NlogN) and each course is pushed and popped at most once, so the whole run is O(Nlog⁡N)O(N \log N)O(NlogN) time and O(N)O(N)O(N) space.

  • Sorting by duration instead of last_day: shortest-first ignores deadlines. On [[3,3],[1,3],[1,6],[2,3]] shortest-first takes the two 1-day courses, then the 2-day course misses day 3 and gets dropped with the 3-day one: 2 courses instead of 3.

  • Checking before taking: if total_time + duration <= last_day: take it never revisits old picks, so one long early course can block several short later ones. Take the course first, then drop the longest if the deadline breaks.

  • Dropping the course just added instead of the longest: the new course may be short; dropping the longest one frees the most days for later courses while keeping the same count.

  • Returning total_time: the question asks how many courses; the answer is len(max_heap).

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

You will see how a senior engineer recognizes a regret greedy and explains why dropping the longest course is safe.

Pattern Recognition Signals

The 10-second spot

"The largest number of courses", each with a length and a last day, done one at a time: you pick as many items as possible under deadlines, and a pick that looked fine early can block several later ones. That is the signal to take greedily and keep a heap to undo the worst pick: Greedy with Regret.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

After each course, max_heap holds the most courses that all finish by their last days, and among all sets of that size it has the smallest total_time; the rule is: push -duration, and if total_time > last_day, pop the longest and subtract it.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • courses.sort(key=lambda c: c[1]): sort by last_day, not by duration, or a short course with a late deadline pushes out a course whose early deadline was still reachable.

  • Take first, then check: heapq.heappush(max_heap, -duration) before if total_time > last_day; checking before taking never lets a long early course be swapped out.

  • longest = -heapq.heappop(max_heap): Python's heap is a min-heap, so durations are stored negated; forgetting the minus pops the shortest course instead of the longest.

  • return len(max_heap), not total_time.

The 60-Second Interview Pitch

Say this out loud before you type a single line

I'd use Greedy with Regret. I sort the courses by their last day and walk through them, taking every course: I add its duration to a running total and push it onto a max-heap. If the total now passes this course's last day, I pop the longest course I've taken and subtract it. That keeps the count the same as before this course but makes the total as small as possible, so every later course has the most room to fit; and since all earlier courses have earlier deadlines, they still fit too. The trap is the sort key: it must be the last day, not the duration, or a short course with a late deadline can crowd out one whose early deadline was reachable. The answer is the heap's size. Sorting and the heap give O(N log N) time and O(N) space.

So: sort by last_day, take first, pop the longest when total_time > last_day, return len(max_heap).

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(N log N)

Look at the code: courses.sort(key=lambda c: c[1]) costs O(N log N). The loop runs once per course; each iteration does one heappush (O(log N)) and at most one heappop (O(log N)) on a heap that never holds more than N items. Total: O(N log N) + N · O(log N) = O(N log N).

SPACE COMPLEXITY

O(N)

max_heap holds at most N negated durations: O(N). Python's sort needs up to O(N) extra memory as well. The answer is one integer.

Formal Recurrence Relation

T(N) = O(N log N) + N · (O(log N) + O(log N)) = O(N log N)

Look at the code: courses.sort(key=lambda c: c[1]) costs O(N log N). The loop runs once per course; each iteration does one heappush (O(log N)) and at most one heappop (O(log N)) on a heap that never holds more than N items. Total: O(N log N) + N · O(log N) = O(N log N).

Derivation Progression

Sort by last day

O(N log N)

courses.sort(key=lambda c: c[1]).

Take each course

N × O(log N)

One heappush per course onto a heap of at most N items.

Regret pops

at most N × O(log N)

Each course is popped at most once, since it is pushed once.

Total

O(N log N)

The sort and the heap work have the same order.

Variable Definitions

NNN

Number of courses, len(courses)

Memory Architecture & Bounds

🟣 Call Stack

O(1) Iterative, no recursion

🔵 Auxiliary Heap

O(N): max_heap plus the sort's working memory

🟢 Output Space

O(1): one integer

Boundary Best / Worst Cases

Best Case

O(Nlog⁡N)O(N \log N)O(NlogN): the sort runs even if no course is ever popped

Average Case

O(Nlog⁡N)O(N \log N)O(NlogN)

Worst Case

O(Nlog⁡N)O(N \log N)O(NlogN)

Binary Heap Priority Queue Tree

Binary Heap Priority Queue Tree
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Triggers: "the largest number of courses", "finished no later than day lastDay", "one course at a time". Count-maximizing selection under deadlines, where an early pick can block later ones: Greedy with Regret (take everything, undo the longest with a max-heap).

CONSTRAINTS & BOUNDS

N≤104N \le 10^4N≤104 courses, durations and last days ≤104\le 10^4≤104. Brute force over subsets is 2N2^N2N; the budget is O(Nlog⁡N)O(N \log N)O(NlogN) time and O(N)O(N)O(N) space.

FAANG PRODUCTION TRAPS & EDGE CASES

Sorting by duration instead of last day. At scale, the heap holds every accepted item; if items arrive as a stream sorted by due time, the same loop runs online, but items that arrive out of due-time order break the invariant, so buffer and sort them first.

Core Algorithmic State Invariants

1. Best Set So Far

After each course, `max_heap` holds the most courses that all finish by their last days, and among all sets of that size it has the smallest `total_time`.

2. Regret the Longest

When `total_time > last_day`, popping the longest course restores the deadline (it is at least as long as the new course) and frees the most days, so no later course is blocked by a choice we could have made better.

3. Deadline Order

Sorting by `last_day` means a set that fits the current last day also fits every earlier one. Sort O(N log N) plus one push and at most one pop per course: O(N log N) time, O(N) space.

Theory Context•Priority Queue / Heap
HardLC 630

Course Schedule III (LeetCode 630)

You will see how taking every course and undoing the longest one when a deadline breaks finds the largest schedule in O(N log N).

Target Frequency:GoogleAmazonMicrosoft

You are offered n online courses. courses[i] = [duration_i, lastDay_i]: course i takes duration_i days in a row, and it has to be finished no later than day lastDay_i. You start on day 1 and can only follow one course at a time, so the courses you choose run back to back.

Return the largest number of courses you can complete.

Worked Examples

Example 1
Input:courses = [[100,200],[200,1300],[1000,1250],[2000,3200]]
Output:3
[100,200][1000,1250][200,1300]11300
Explanation: Take the 100-day course (done on day 100), then the 1000-day course (done on day 1100), then the 200-day course (done on day 1300). The 2000-day course would end on day 3300, after its last day 3200.
Example 2
Input:courses = [[1,2]]
Output:1
Explanation: The only course ends on day 1, before its last day 2.
Example 3
Input:courses = [[3,2],[4,3]]
Output:0
Explanation: Each course is longer than the days available before its last day, so none can be finished.

⚖️Formal Constraints & Bounds

  • 1 <= courses.length <= 104

  • 1 <= duration_i, lastDay_i <= 104

Deep-Dive & Conceptual Insights

Why It Works & Core Invariant

Go through the courses by last day and take each one; when the total runs past the current last day, dropping the longest course taken keeps the same count and frees the most days for what comes next.

Real-World Scenario & Production Applications

Admitting jobs into a queue where each job has a length and a due time: a scheduler that accepts every job, and evicts the longest accepted one whenever a due time would be missed, keeps the number of on-time jobs as high as possible. The same rule appears in classic single-machine scheduling (maximize the number of on-time jobs).

Subproblems & Recurrence Decomposition3 Phases

🧩Subproblem 1: Adjacency List & In-Degree Initialization

Construct directed graph from edge pairs and compute in-degree for every vertex: in_degree[v] = number of prerequisite dependencies.

Mathematical Recurrence / Code Invariant
adj = defaultdict(list)
in_degree = [0] * numCourses
for dest, src in prerequisites:
    adj[src].append(dest)
    in_degree[dest] += 1

Step-by-Step Execution Trace Table

Input courses = [[100,200],[200,1300],[1000,1250],[2000,3200]], sorted by last day: [100,200], [1000,1250], [200,1300], [2000,3200].

StepCourse [duration, last_day]total_time after taking ittotal_time > last_day?Actionmax_heap (durations taken)
1[100, 200]100NoKeep{100}
2[1000, 1250]1100NoKeep{1000, 100}
3[200, 1300]1300No (equal is fine)Keep{1000, 200, 100}
4[2000, 3200]3300YesPop the longest, 2000: total_time = 1300{1000, 200, 100}
Endreturn len(max_heap) = 3
Scroll horizontally to see all columns, or expand to full screen
Core Invariant Specification & Code Shape
Archetype Code Shape
Python
1Take every course, and when the days run out, undo the single worst choice made so far: the longest course taken.
2`max_heap` always holds the most courses that fit the deadlines seen so far, with the smallest possible `total_time`.
3Sort `courses` by last day; for each `duration, last_day`: add `duration` to `total_time`, push `-duration`; if `total_time > last_day`, pop the longest and subtract it; `return len(max_heap)`.
4The trap: sort by `last_day`, not by `duration`, or a short course with a late deadline pushes out a course whose early deadline was still reachable.

Target: Course Schedule III (LeetCode 630). Checking deadlines in increasing order means that when a course fits its own last day, every earlier course (with an earlier last day) fits too.

Boundary Model: Complete Binary Tree / Min-Max Heap Invariant

Heap maintains extreme element at root heap[0]. heappushpop maintains size bounded to K in O(log K) time.

Loop Invariant Termination

heapq.heappush(h, val); if len(h) > k: heapq.heappop(h) — peek min/max in O(1).

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

Some greedy problems can't be solved by picking the right item up front, because you only learn later that an earlier pick was a mistake. Greedy with Regret takes every item as it comes, keeps the picks in a heap, and the moment a limit is broken it undoes the single worst pick. For Course Schedule III: go through courses by deadline, take each one, and if the running total of days now misses the current deadline, drop the longest course taken so far.

🏟️ The Analogy: Packing a Suitcase Against a Weight Limit

You pack items in the order you need them, and at each checkpoint the bag must be under the limit. Whenever you are over, you don't unpack everything and start again: you pull out the single heaviest item. The count of items stays as high as it can be, and the bag is as light as it can be, which leaves the most room for what comes next.

🪄 The Mathematical Harmony / Magic Trick
Code / Blueprint
courses.sort(key=lambda c: c[1]) # by last day
for duration, last_day in courses:
total_time += duration
heapq.heappush(max_heap, -duration) # take it first
if total_time > last_day:
total_time -= -heapq.heappop(max_heap) # regret the longest
return len(max_heap)
 

After each course, max_heap holds the most courses that fit the deadlines seen so far, and among all such sets the one with the smallest total_time. Dropping the longest course keeps the count and frees the most days, so no later course is ever blocked by a choice we could have made better.

💡 Summary

Order by the constraint (the deadline), take greedily, and let a max-heap undo the worst choice when the constraint breaks. Sorting costs O(Nlog⁡N)O(N \log N)O(NlogN) and each course is pushed and popped at most once, so the whole run is O(Nlog⁡N)O(N \log N)O(NlogN) time and O(N)O(N)O(N) space.

  • Sorting by duration instead of last_day: shortest-first ignores deadlines. On [[3,3],[1,3],[1,6],[2,3]] shortest-first takes the two 1-day courses, then the 2-day course misses day 3 and gets dropped with the 3-day one: 2 courses instead of 3.

  • Checking before taking: if total_time + duration <= last_day: take it never revisits old picks, so one long early course can block several short later ones. Take the course first, then drop the longest if the deadline breaks.

  • Dropping the course just added instead of the longest: the new course may be short; dropping the longest one frees the most days for later courses while keeping the same count.

  • Returning total_time: the question asks how many courses; the answer is len(max_heap).

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

You will see how a senior engineer recognizes a regret greedy and explains why dropping the longest course is safe.

Pattern Recognition Signals

The 10-second spot

"The largest number of courses", each with a length and a last day, done one at a time: you pick as many items as possible under deadlines, and a pick that looked fine early can block several later ones. That is the signal to take greedily and keep a heap to undo the worst pick: Greedy with Regret.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

After each course, max_heap holds the most courses that all finish by their last days, and among all sets of that size it has the smallest total_time; the rule is: push -duration, and if total_time > last_day, pop the longest and subtract it.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • courses.sort(key=lambda c: c[1]): sort by last_day, not by duration, or a short course with a late deadline pushes out a course whose early deadline was still reachable.

  • Take first, then check: heapq.heappush(max_heap, -duration) before if total_time > last_day; checking before taking never lets a long early course be swapped out.

  • longest = -heapq.heappop(max_heap): Python's heap is a min-heap, so durations are stored negated; forgetting the minus pops the shortest course instead of the longest.

  • return len(max_heap), not total_time.

The 60-Second Interview Pitch

Say this out loud before you type a single line

I'd use Greedy with Regret. I sort the courses by their last day and walk through them, taking every course: I add its duration to a running total and push it onto a max-heap. If the total now passes this course's last day, I pop the longest course I've taken and subtract it. That keeps the count the same as before this course but makes the total as small as possible, so every later course has the most room to fit; and since all earlier courses have earlier deadlines, they still fit too. The trap is the sort key: it must be the last day, not the duration, or a short course with a late deadline can crowd out one whose early deadline was reachable. The answer is the heap's size. Sorting and the heap give O(N log N) time and O(N) space.

So: sort by last_day, take first, pop the longest when total_time > last_day, return len(max_heap).

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(N log N)

Look at the code: courses.sort(key=lambda c: c[1]) costs O(N log N). The loop runs once per course; each iteration does one heappush (O(log N)) and at most one heappop (O(log N)) on a heap that never holds more than N items. Total: O(N log N) + N · O(log N) = O(N log N).

SPACE COMPLEXITY

O(N)

max_heap holds at most N negated durations: O(N). Python's sort needs up to O(N) extra memory as well. The answer is one integer.

Formal Recurrence Relation

T(N) = O(N log N) + N · (O(log N) + O(log N)) = O(N log N)

Look at the code: courses.sort(key=lambda c: c[1]) costs O(N log N). The loop runs once per course; each iteration does one heappush (O(log N)) and at most one heappop (O(log N)) on a heap that never holds more than N items. Total: O(N log N) + N · O(log N) = O(N log N).

Derivation Progression

Sort by last day

O(N log N)

courses.sort(key=lambda c: c[1]).

Take each course

N × O(log N)

One heappush per course onto a heap of at most N items.

Regret pops

at most N × O(log N)

Each course is popped at most once, since it is pushed once.

Total

O(N log N)

The sort and the heap work have the same order.

Variable Definitions

NNN

Number of courses, len(courses)

Memory Architecture & Bounds

🟣 Call Stack

O(1) Iterative, no recursion

🔵 Auxiliary Heap

O(N): max_heap plus the sort's working memory

🟢 Output Space

O(1): one integer

Boundary Best / Worst Cases

Best Case

O(Nlog⁡N)O(N \log N)O(NlogN): the sort runs even if no course is ever popped

Average Case

O(Nlog⁡N)O(N \log N)O(NlogN)

Worst Case

O(Nlog⁡N)O(N \log N)O(NlogN)

Binary Heap Priority Queue Tree

Binary Heap Priority Queue Tree
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Triggers: "the largest number of courses", "finished no later than day lastDay", "one course at a time". Count-maximizing selection under deadlines, where an early pick can block later ones: Greedy with Regret (take everything, undo the longest with a max-heap).

CONSTRAINTS & BOUNDS

N≤104N \le 10^4N≤104 courses, durations and last days ≤104\le 10^4≤104. Brute force over subsets is 2N2^N2N; the budget is O(Nlog⁡N)O(N \log N)O(NlogN) time and O(N)O(N)O(N) space.

FAANG PRODUCTION TRAPS & EDGE CASES

Sorting by duration instead of last day. At scale, the heap holds every accepted item; if items arrive as a stream sorted by due time, the same loop runs online, but items that arrive out of due-time order break the invariant, so buffer and sort them first.

Core Algorithmic State Invariants

1. Best Set So Far

After each course, `max_heap` holds the most courses that all finish by their last days, and among all sets of that size it has the smallest `total_time`.

2. Regret the Longest

When `total_time > last_day`, popping the longest course restores the deadline (it is at least as long as the new course) and frees the most days, so no later course is blocked by a choice we could have made better.

3. Deadline Order

Sorting by `last_day` means a set that fits the current last day also fits every earlier one. Sort O(N log N) plus one push and at most one pop per course: O(N log N) time, O(N) space.

Rosetta Dual-Monaco Comparison
Python 3
CANONICAL INVARIANT TEMPLATE
Loading...
CONCRETE: COURSE SCHEDULE III (LEETCODE 630)
T = O(N log N)S = O(N)
Loading...
Pattern Implementation Mapping TableCanonical Invariant ⟷ Concrete Code ⟷ Engineering Rationale
Canonical InvariantConcrete CodeEngineering Rationale
Order items by the limit they must meetcourses.sort(key=lambda c: c[1])Checking deadlines in increasing order means that when a course fits its own last day, every earlier course (with an earlier last day) fits too.
A heap of the picks so far, worst on topmax_heap: list[int] = []Python's heapq is a min-heap, so durations are stored negated to put the longest course on top.
Take every item firsttotal_time += duration heapq.heappush(max_heap, -duration)Taking first and fixing later is what makes the greedy safe: no decision is final until the limit breaks.
When the limit breaks, undo the single worst pickif total_time > last_day: longest = -heapq.heappop(max_heap) total_time -= longestRemoving the longest course restores the deadline (it is at least as long as the new course) and leaves the smallest possible total time.
Answer = how many picks survivedreturn len(max_heap)Each course stays in the heap only while it fits, so the heap size is the number of courses taken.
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•