Skip to content

Commit 1026a97

Browse files
committed
Update Notes
1 parent 95de828 commit 1026a97

26 files changed

Lines changed: 2746 additions & 336 deletions

pages/Abstract Factory Pattern.md

Lines changed: 452 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
---
2+
seoTitle: Activity Selection Problem – Greedy Interval Scheduling Algorithm Guide
3+
description: "Master the Activity Selection Problem using greedy interval scheduling. Covers earliest finish time strategy, exchange argument proof, weighted variant with DP, and full implementations in Python, C++, JavaScript, and Java."
4+
keywords: "activity selection problem, interval scheduling, greedy algorithm, earliest finish time, non-overlapping intervals, weighted job scheduling, DP, Python, C++, Java, JavaScript, DSA"
5+
displayTitle: Activity Selection Problem
6+
---
7+
8+
> [!info] What is the Activity Selection Problem?
9+
> The **Activity Selection Problem** asks: given $N$ activities with start times $s_i$ and finish times $f_i$, select the **maximum number of non-overlapping activities** a single resource can perform.
10+
> The [[Greedy Algorithm Concepts|greedy]] strategy — **always pick the activity that finishes earliest** — is provably optimal and runs in **$O(N \log N)$** time (dominated by sorting).
11+
> This is the canonical problem for proving greedy correctness using the **exchange argument**.
12+
13+
- # Explanation
14+
collapsed:: true
15+
- Two activities $i$ and $j$ are **compatible** if they do not overlap: $f_i \leq s_j$ or $f_j \leq s_i$.
16+
- The goal is to find the **largest compatible subset** of activities.
17+
-
18+
- ## Why Earliest Finish Time is Greedy-Optimal
19+
collapsed:: true
20+
- **Intuition**: Finishing early leaves maximum time for future activities.
21+
- **Exchange Argument Proof**:
22+
1. Let OPT be any optimal solution. Let $a_1$ be the first activity picked by OPT.
23+
2. Let $g_1$ be the first activity picked by greedy (earliest finish).
24+
3. Since greedy picks earliest finish: $f(g_1) \leq f(a_1)$.
25+
4. Replace $a_1$ with $g_1$ in OPT → result is still valid (same or more time freed up) and still optimal size.
26+
5. Repeat → greedy solution matches OPT in size. ✅
27+
-
28+
- ## Activity Selection vs Interval Scheduling Variants
29+
collapsed:: true
30+
- | Variant | Goal | Strategy |
31+
|---------|------|----------|
32+
| **Max Activities (unweighted)** | Max number of non-overlapping activities | Greedy: sort by finish time |
33+
| **Interval Coloring** | Min resources (rooms/machines) | Greedy: sort by start time, assign to available resource |
34+
| **Weighted Job Scheduling** | Max total weight of non-overlapping jobs | [[Dynamic Programming Concepts]]: DP + binary search |
35+
| **Min Interval Coverage** | Fewest intervals to cover a range | Greedy: always pick interval extending coverage farthest |
36+
-
37+
- ## Core Properties
38+
collapsed:: true
39+
- **Stability**: Not applicable.
40+
- **Greedy Choice Property**: Selecting the activity with the earliest finish time never eliminates a globally optimal set.
41+
- **Optimal Substructure**: After selecting activity $k$, the remaining problem is the same — find max activities compatible with $k$ from the remaining set.
42+
43+
- # How It Works
44+
collapsed:: true
45+
- ## The Core Idea
46+
collapsed:: true
47+
- 1. **Sort** all activities by finish time.
48+
- 2. Always **select the first compatible activity** (the one that starts after the last selected activity finishes).
49+
- 3. Repeat until no more activities remain.
50+
-
51+
- ```mermaid
52+
flowchart TD
53+
A["Sort activities by finish time f_i"] --> B["Select first activity\n(always included in optimal)"]
54+
B --> C["last_finish = f[0]"]
55+
C --> D["For each remaining activity i (sorted by finish)"]
56+
D --> E{"s[i] >= last_finish?\n(compatible with last selected)"}
57+
E -- Yes --> F["Select activity i\nlast_finish = f[i]"]
58+
E -- No --> G["Skip activity i\n(overlaps with last selected)"]
59+
F --> D
60+
G --> D
61+
D --> H["✅ Selected set = maximum non-overlapping activities"]
62+
63+
classDef default fill:#1f2937,stroke:#3b82f6,stroke-width:2px,color:#fff;
64+
```
65+
-
66+
- ## Step-by-Step Trace
67+
collapsed:: true
68+
- ```
69+
Activities (start, finish):
70+
A1=(1,4), A2=(3,5), A3=(0,6), A4=(5,7), A5=(3,9), A6=(5,9), A7=(6,10), A8=(8,11), A9=(8,12), A10=(2,14)
71+
72+
After sorting by finish time:
73+
A1=(1,4), A2=(3,5), A3=(0,6), A4=(5,7), A5=(3,9), A6=(5,9), A7=(6,10), A8=(8,11), A9=(8,12), A10=(2,14)
74+
75+
Step 1: Select A1 (finish=4). last_finish=4
76+
Step 2: A2 start=3 < 4 → skip
77+
Step 3: A3 start=0 < 4 → skip
78+
Step 4: A4 start=5 >= 4 → SELECT A4. last_finish=7
79+
Step 5: A5 start=3 < 7 → skip
80+
Step 6: A6 start=5 < 7 → skip
81+
Step 7: A7 start=6 < 7 → skip
82+
Step 8: A8 start=8 >= 7 → SELECT A8. last_finish=11
83+
Step 9: A9 start=8 < 11 → skip
84+
Step 10: A10 start=2 < 11 → skip
85+
86+
Selected: {A1, A4, A8} → 3 activities ✅
87+
```
88+
89+
- # Complexity Analysis
90+
collapsed:: true
91+
- | Scenario | Time Complexity | Space Complexity | Notes |
92+
|---|---|---|---|
93+
| **Unweighted (Greedy)** | **O(N log N)** | **O(N)** | Dominated by sort; O(1) extra after |
94+
| **Pre-sorted input** | **O(N)** | **O(1)** | Single pass after sort |
95+
| **Weighted (DP + BSearch)** | **O(N log N)** | **O(N)** | DP table + binary search for prev compatible |
96+
-
97+
- ## Why O(N log N)?
98+
collapsed:: true
99+
- Sorting takes $O(N \log N)$. The greedy sweep is a single linear pass $O(N)$. Total = $O(N \log N)$.
100+
- If activities are already sorted by finish time, the entire algorithm runs in $O(N)$.
101+
102+
- # Implementation
103+
collapsed:: true
104+
- > [!note] Activity Selection — Greedy (Unweighted) + Weighted DP Variant
105+
> The standard greedy returns the count and selected activities. The weighted variant uses DP + binary search to maximize total profit.
106+
- Languages: [[Python]] · [[Cpp]] · [[Java Script]] · [[Java]]
107+
-
108+
- :::code-tabs
109+
110+
```python
111+
def activity_selection(activities: list[tuple[int, int]]) -> list[tuple[int, int]]:
112+
"""
113+
Greedy Activity Selection — unweighted.
114+
activities: list of (start, finish) tuples.
115+
Returns list of selected (start, finish) pairs.
116+
"""
117+
# Sort by finish time
118+
sorted_acts = sorted(activities, key=lambda x: x[1])
119+
selected = [sorted_acts[0]]
120+
last_finish = sorted_acts[0][1]
121+
122+
for start, finish in sorted_acts[1:]:
123+
if start >= last_finish:
124+
selected.append((start, finish))
125+
last_finish = finish
126+
127+
return selected
128+
129+
# Weighted Job Scheduling — O(N log N) DP
130+
from bisect import bisect_right
131+
132+
def weighted_job_scheduling(jobs: list[tuple[int, int, int]]) -> int:
133+
"""
134+
jobs: list of (start, finish, profit).
135+
Returns maximum total profit of non-overlapping jobs.
136+
"""
137+
jobs.sort(key=lambda x: x[1]) # sort by finish time
138+
n = len(jobs)
139+
finish_times = [j[1] for j in jobs]
140+
dp = [0] * (n + 1)
141+
142+
for i in range(1, n + 1):
143+
start, finish, profit = jobs[i - 1]
144+
# Find last job that doesn't conflict (finish <= start of current)
145+
j = bisect_right(finish_times, start, 0, i - 1)
146+
# Either include job i (profit + dp[j]) or exclude it (dp[i-1])
147+
dp[i] = max(dp[i - 1], profit + dp[j])
148+
149+
return dp[n]
150+
151+
# Examples
152+
activities = [(1,4),(3,5),(0,6),(5,7),(3,9),(5,9),(6,10),(8,11),(8,12),(2,14)]
153+
selected = activity_selection(activities)
154+
print(f"Selected: {selected}") # [(1,4), (5,7), (8,11)]
155+
print(f"Count: {len(selected)}") # 3
156+
157+
jobs = [(1, 4, 20), (3, 5, 30), (0, 6, 15), (5, 7, 40)]
158+
print(f"Max Profit: {weighted_job_scheduling(jobs)}") # 60 (job1 + job4)
159+
```
160+
161+
```c++
162+
#include <iostream>
163+
#include <vector>
164+
#include <algorithm>
165+
166+
using Activity = std::pair<int, int>; // (start, finish)
167+
168+
std::vector<Activity> activitySelection(std::vector<Activity> activities) {
169+
std::sort(activities.begin(), activities.end(),
170+
[](const Activity& a, const Activity& b) { return a.second < b.second; });
171+
172+
std::vector<Activity> selected = {activities[0]};
173+
int lastFinish = activities[0].second;
174+
175+
for (size_t i = 1; i < activities.size(); ++i) {
176+
if (activities[i].first >= lastFinish) {
177+
selected.push_back(activities[i]);
178+
lastFinish = activities[i].second;
179+
}
180+
}
181+
return selected;
182+
}
183+
184+
int main() {
185+
std::vector<Activity> acts = {{1,4},{3,5},{0,6},{5,7},{3,9},{5,9},{6,10},{8,11}};
186+
auto result = activitySelection(acts);
187+
std::cout << "Selected " << result.size() << " activities:\n";
188+
for (auto [s, f] : result)
189+
std::cout << " (" << s << ", " << f << ")\n";
190+
return 0;
191+
}
192+
```
193+
194+
```javascript
195+
function activitySelection(activities) {
196+
// activities: [{start, finish}, ...]
197+
activities.sort((a, b) => a.finish - b.finish);
198+
const selected = [activities[0]];
199+
let lastFinish = activities[0].finish;
200+
201+
for (let i = 1; i < activities.length; i++) {
202+
if (activities[i].start >= lastFinish) {
203+
selected.push(activities[i]);
204+
lastFinish = activities[i].finish;
205+
}
206+
}
207+
return selected;
208+
}
209+
210+
const acts = [
211+
{start:1,finish:4},{start:3,finish:5},{start:0,finish:6},
212+
{start:5,finish:7},{start:8,finish:11}
213+
];
214+
const result = activitySelection(acts);
215+
console.log("Count:", result.length); // 3
216+
console.log("Selected:", result);
217+
```
218+
219+
```java
220+
import java.util.*;
221+
222+
public class ActivitySelection {
223+
public static List<int[]> select(int[][] activities) {
224+
// activities[i] = {start, finish}
225+
Arrays.sort(activities, (a, b) -> a[1] - b[1]);
226+
List<int[]> selected = new ArrayList<>();
227+
selected.add(activities[0]);
228+
int lastFinish = activities[0][1];
229+
230+
for (int i = 1; i < activities.length; i++) {
231+
if (activities[i][0] >= lastFinish) {
232+
selected.add(activities[i]);
233+
lastFinish = activities[i][1];
234+
}
235+
}
236+
return selected;
237+
}
238+
239+
public static void main(String[] args) {
240+
int[][] acts = {{1,4},{3,5},{0,6},{5,7},{3,9},{8,11}};
241+
List<int[]> result = select(acts);
242+
System.out.println("Count: " + result.size());
243+
for (int[] a : result)
244+
System.out.println(" (" + a[0] + ", " + a[1] + ")");
245+
}
246+
}
247+
```
248+
249+
:::
250+
251+
- # Alternative Variant (Interval Coloring — Minimum Machines)
252+
collapsed:: true
253+
- > [!tip] Interval Coloring — Find Minimum Number of Machines Needed
254+
> Given $N$ activities, what is the minimum number of machines (rooms, processors) needed so all activities run simultaneously? Greedy: **sort by start time**, maintain a min-heap of finish times. If a machine is free (heap top ≤ current start), reuse it; otherwise add a new machine.
255+
-
256+
- :::code-tabs
257+
258+
```python
259+
import heapq
260+
261+
def min_machines(activities: list[tuple[int, int]]) -> int:
262+
"""
263+
Minimum machines to run all activities without conflicts.
264+
Greedy: sort by start, use min-heap of finish times.
265+
"""
266+
if not activities:
267+
return 0
268+
activities.sort(key=lambda x: x[0]) # sort by start time
269+
heap = [] # min-heap of finish times (machines in use)
270+
271+
for start, finish in activities:
272+
if heap and heap[0] <= start:
273+
heapq.heapreplace(heap, finish) # reuse freed machine
274+
else:
275+
heapq.heappush(heap, finish) # add new machine
276+
277+
return len(heap)
278+
279+
# Example
280+
activities = [(0,6),(1,4),(3,5),(5,7),(3,9),(5,9),(6,10),(8,11)]
281+
print(f"Min machines: {min_machines(activities)}") # 3
282+
```
283+
284+
:::
285+
286+
- # When to Use Activity Selection
287+
collapsed:: true
288+
- ```mermaid
289+
flowchart TD
290+
Q{"Do you have intervals\nwith start/finish times?"}
291+
Q -- No --> R1["Use standard sorting\nor scheduling algorithms"]
292+
Q -- Yes --> S1{"Are all activities\nequally valuable (unweighted)?"}
293+
S1 -- Yes --> R2["✅ Greedy: Sort by finish time\nO(N log N), maximum count"]
294+
S1 -- No --> S2{"Do activities have\ndifferent profits/weights?"}
295+
S2 -- Yes --> R3["✅ Weighted Job Scheduling\nDP + Binary Search, O(N log N)"]
296+
S2 -- No --> R4["✅ Greedy: earliest finish first"]
297+
298+
classDef default fill:#1f2937,stroke:#3b82f6,stroke-width:2px,color:#fff;
299+
```
300+
-
301+
- ## ✅ Use Activity Selection When
302+
- Maximizing the **count** of non-overlapping intervals (all equal value).
303+
- **Room/machine allocation** problems — minimize resources used.
304+
- **Meeting scheduling** — fit the most meetings in a day.
305+
- LeetCode-style problems: "Non-overlapping Intervals", "Meeting Rooms II".
306+
-
307+
- ## ❌ Avoid Simple Greedy When
308+
- Activities have **different profits/weights** — switch to Weighted Job Scheduling DP.
309+
- The constraint is not just non-overlap but involves **dependencies** between activities.
310+
311+
- # Key Takeaways
312+
collapsed:: true
313+
- **Earliest Finish First** — The greedy choice is always picking the activity with the earliest finish time, maximizing remaining time for future activities.
314+
- **Exchange Argument Proven** — Correctness is guaranteed by the exchange argument: any optimal solution can be transformed into the greedy solution without reducing the count.
315+
- **O(N log N) → O(N)** — Sorting dominates; after sorting, a single linear scan is enough.
316+
- **Weighted Variant Needs DP** — When activities have different profits, greedy fails. Use DP + binary search ([[Dynamic Programming Concepts]]) for the weighted version.
317+
- **Interval Coloring Dual** — The minimum number of machines = the maximum overlap depth at any point (use min-heap of finish times).
318+
- **LeetCode Applications** — "Non-overlapping Intervals" (#435), "Meeting Rooms II" (#253), "Minimum Number of Arrows to Burst Balloons" (#452).
319+
320+
- # More Learn
321+
collapsed:: true
322+
- ## GitHub & Webs
323+
- [GeeksforGeeks → Activity Selection Problem](https://www.geeksforgeeks.org/activity-selection-problem-greedy-algo-1/)
324+
- [CP Algorithms → Scheduling](https://cp-algorithms.com/greedy/job_scheduling.html)
325+
- [LeetCode → Non-overlapping Intervals (Problem 435)](https://leetcode.com/problems/non-overlapping-intervals/)
326+
- [LeetCode → Meeting Rooms II (Problem 253)](https://leetcode.com/problems/meeting-rooms-ii/)

0 commit comments

Comments
 (0)