Activity Selection Problem | Greedy Algo-1
Given n
activities with start times in start[]
and finish times in finish[]
, find the maximum number of activities a single person can perform without overlap. A person can only do one activity at a time.
Examples:
Input: start[] = [1, 3, 0, 5, 8, 5], finish[] = [2, 4, 6, 7, 9, 9]
Output: 4
Explanation: A person can perform at most four activities. The maximum set of activities that can be performed is {0, 1, 3, 4} (these are the indexes in thestart[]
andfinish[]
arrays).Input: start[] = [10, 12, 20], finish[] = [20, 25, 30]
Output: 1
Explanation: A person can perform at most one activity.
Table of Content
- [Naive Approach] Generate All Subsets - O(n^2 * 2^n) Time and O(n) Space
- How does Greedy Choice work for Activities sorted according to finish time?
- [Expected Approach 1] - Using Sorting - O(n * log(n)) Time and O(n) Space
- [Expected Approach 2] - Using Priority Queue - O(n * log(n)) Time and O(n) Space
[Naive Approach] Generate All Subsets - O(n^2 * 2^n) Time and O(n) Space
Generates all possible subsets of activities, where each subset represents a potential selection. For each subset, we check if the activities are mutually non-overlapping by comparing their time intervals pairwise. If a subset is valid and contains more activities than our current maximum, we update the maximum count. In the end, we get the size of the largest valid subset.
How does Greedy Choice work for Activities sorted according to finish time?
Let the set of activities be S={1,2,3,…,n} sorted by their finish times. The greedy strategy always selects activity 1 first (the one with the earliest finish time).
Why does activity 1 always lead to an optimal solution?
We can prove this by contradiction: suppose there exists another optimal solution B whose first selected activity is k≠1 Since the activities in B are non-overlapping and k is the earliest finishing activity in B, it follows that:
finish(k) ≥ finish(1)
Now, we can construct a new solution A by replacing k with activity 1 in B:
Let A = (B∖{k}) ∪ {1}
This means: we form set A by taking all activities in B except k, and adding activity 1 instead.
Because activity 1 finishes no later than k, replacing k with 1 does not create any overlaps. Thus, A is also a valid solution with the same size as B, but it starts with activity 1.
This shows that there always exists an optimal solution that begins with the activity that finishes earliest (activity 1).
[Expected Approach 1] - Using Sorting - O(n * log(n)) Time and O(n) Space
The greedy strategy is to always pick the next activity that has the earliest finish time among the remaining activities and starts after the previously selected activity finishes. By sorting the activities based on their finish times, we ensure that at each step, we select the activity with the minimum finishing time available.
Step By Step Implementations:
- Sort the activities according to their finishing time
- Select the first activity from the sorted array
- Do the following for the remaining activities in the sorted array
- If the start time of this activity is greater than or equal to the finish time of the previously selected activity then select this activity
// C++ program for activity selection problem
#include <bits/stdc++.h>
using namespace std;
// Function to solve the activity selection problem
int activitySelection(vector<int> &start, vector<int> &finish)
{
vector<vector<int>> arr;
for (int i = 0; i < start.size(); i++) {
arr.push_back({start[i], finish[i]});
}
// Sort activities by finish time
sort(arr.begin(), arr.end(),
[](const vector<int>& a, const vector<int>& b) {
return a[1] < b[1];
});
// At least one activity can be performed
int count = 1;
// Index of last selected activity
int j = 0;
for (int i = 1; i < arr.size(); i++) {
// Check if current activity starts
// after last selected activity finishes
if (arr[i][0] > arr[j][1]) {
count++;
// Update last selected activity
j = i;
}
}
return count;
}
int main()
{
vector<int> start = {1, 3, 0, 5, 8, 5};
vector<int> finish = {2, 4, 6, 7, 9, 9};
cout << activitySelection(start, finish);
return 0;
}
// Java program for activity selection problem
import java.util.*;
public class GfG {
// Function to solve the activity selection problem
public static int activitySelection(int[] start, int[] finish) {
int n = start.length;
int[][] arr = new int[n][2];
for (int i = 0; i < n; i++) {
arr[i][0] = start[i];
arr[i][1] = finish[i];
}
// Sort activities by finish time
Arrays.sort(arr, Comparator.comparingInt(a -> a[1]));
// At least one activity can be performed
int count = 1;
// Index of last selected activity
int j = 0;
for (int i = 1; i < n; i++) {
// Check if current activity starts
// after last selected activity finishes
if (arr[i][0] > arr[j][1]) {
count++;
// Update last selected activity
j = i;
}
}
return count;
}
public static void main(String[] args) {
int[] start = {1, 3, 0, 5, 8, 5};
int[] finish = {2, 4, 6, 7, 9, 9};
System.out.println(activitySelection(start, finish));
}
}
# Python program for activity selection problem
def activitySelection(start, finish):
arr = list(zip(start, finish))
# Sort activities by finish time
arr.sort(key=lambda x: x[1])
# At least one activity can be performed
count = 1
# Index of last selected activity
j = 0
for i in range(1, len(arr)):
# Check if current activity starts
# after last selected activity finishes
if arr[i][0] > arr[j][1]:
count += 1
# Update last selected activity
j = i
return count
if __name__ == '__main__':
start = [1, 3, 0, 5, 8, 5]
finish = [2, 4, 6, 7, 9, 9]
print(activitySelection(start, finish))
// Function to solve the activity selection problem
int activitySelection(int[] start, int[] finish)
{
List<List<int>> arr = new List<List<int>>();
for (int i = 0; i < start.Length; i++) {
arr.Add(new List<int> { start[i], finish[i] });
}
// Sort activities by finish time
arr.Sort((a, b) => a[1].CompareTo(b[1]));
// At least one activity can be performed
int count = 1;
// Index of last selected activity
int j = 0;
for (int i = 1; i < arr.Count; i++) {
// Check if current activity starts
// after last selected activity finishes
if (arr[i][0] > arr[j][1]) {
count++;
// Update last selected activity
j = i;
}
}
return count;
}
public static void Main()
{
int[] start = new int[] { 1, 3, 0, 5, 8, 5 };
int[] finish = new int[] { 2, 4, 6, 7, 9, 9 };
Console.WriteLine(activitySelection(start, finish));
}
// Function to solve the activity selection problem
function activitySelection(start, finish) {
let arr = [];
for (let i = 0; i < start.length; i++) {
arr.push([start[i], finish[i]]);
}
// Sort activities by finish time
arr.sort((a, b) => a[1] - b[1]);
// At least one activity can be performed
let count = 1;
// Index of last selected activity
let j = 0;
for (let i = 1; i < arr.length; i++) {
// Check if current activity starts
// after last selected activity finishes
if (arr[i][0] > arr[j][1]) {
count++;
// Update last selected activity
j = i;
}
}
return count;
}
const start = [1, 3, 0, 5, 8, 5];
const finish = [2, 4, 6, 7, 9, 9];
console.log(activitySelection(start, finish));
Output
4
[Expected Approach 2] - Using Priority Queue - O(n * log(n)) Time and O(n) Space
We can use Min-Heap to get the activity with minimum finish time. Min-Heap can be implemented using priority-queue
Step By Step Implementations:
- Create a priority queue (min-heap) and push all activities into it, prioritized by their finish times.
- Pop the top activity from the priority queue and add it to the answer vector. Set
finish
to the finish time of this activity. - While the priority queue is not empty, do the following:
- Take the top activity from the priority queue.
- If the start time of this activity is greater than or equal to
finish
, add it to the answer vector and updatefinish
to this activity’s finish time. - Otherwise, ignore the activity.
- After processing all activities, print the selected activities stored in the answer vector.
// C++ program for activity selection problem
// when input activities may not be sorted.
#include <bits/stdc++.h>
using namespace std;
// Function to solve the activity selection problem
int activitySelection(vector<int> &start, vector<int> &finish)
{
// to store results.
int ans = 0;
// Minimum Priority Queue to sort activities in
// ascending order of finishing time (end[i]).
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> p;
for (int i = 0; i < start.size(); i++)
{
p.push(make_pair(finish[i], start[i]));
}
// to store the end time of last activity
int finishtime = -1;
while (!p.empty())
{
pair<int, int> activity = p.top();
p.pop();
if (activity.second > finishtime)
{
finishtime = activity.first;
ans++;
}
}
return ans;
}
int main()
{
vector<int> start = {1, 3, 0, 5, 8, 5};
vector<int> finish = {2, 4, 6, 7, 9, 9};
cout << activitySelection(start, finish);
return 0;
}
import java.util.PriorityQueue;
class GfG {
// Function to solve the activity selection problem
static int activitySelection(int[] start, int[] finish)
{
int n = start.length;
int ans = 0;
// Min Heap to store activities in ascending order
// of finish time
PriorityQueue<int[]> p = new PriorityQueue<>(
(a, b) -> Integer.compare(a[0], b[0]));
for (int i = 0; i < n; i++) {
p.add(new int[] { finish[i], start[i] });
}
// Variable to store the end time of the last
// selected activity
int finishtime = -1;
while (!p.isEmpty()) {
int[] activity
= p.poll(); // Extract the activity with the
// smallest finish time
if (activity[1] > finishtime) {
finishtime = activity[0];
ans++;
}
}
return ans;
}
public static void main(String[] args)
{
int[] start = { 1, 3, 0, 5, 8, 5 };
int[] finish = { 2, 4, 6, 7, 9, 9 };
System.out.println(
activitySelection(start, finish));
}
}
# Python program for activity selection problem
# when input activities may not be sorted.
import heapq
# Function to solve the activity selection problem
def activitySelection(start, finish):
# to store results.
ans = 0
# Minimum Priority Queue to sort activities in
# ascending order of finishing time (end[i]).
p = []
for i in range(len(start)):
heapq.heappush(p, (finish[i], start[i]))
# to store the end time of last activity
finishtime = -1
while p:
activity = heapq.heappop(p)
if activity[1] > finishtime:
finishtime = activity[0]
ans += 1
return ans
if __name__ == "__main__":
start = [1, 3, 0, 5, 8, 5]
finish = [2, 4, 6, 7, 9, 9]
print(activitySelection(start, finish))
using System;
class GfG {
// Custom Min-Heap for sorting activities by finish time
class MinHeap {
private int[, ] heap;
private int size;
public MinHeap(int capacity)
{
heap
= new int[capacity, 2]; // 2D array to store
// (finish, start)
size = 0;
}
// Insert (finish, start) into heap
public void Insert(int finish, int start)
{
heap[size, 0] = finish;
heap[size, 1] = start;
size++;
HeapifyUp(size - 1);
}
// Extract min (smallest finish time)
public(int, int) ExtractMin()
{
if (size == 0)
return (-1, -1); // Empty heap
int minFinish = heap[0, 0];
int minStart = heap[0, 1];
// Move last element to root
heap[0, 0] = heap[size - 1, 0];
heap[0, 1] = heap[size - 1, 1];
size--;
HeapifyDown(0); // Restore heap property
return (minFinish, minStart);
}
public bool IsEmpty() { return size == 0; }
// Maintain heap property (heapify up)
private void HeapifyUp(int index)
{
while (index > 0) {
int parent = (index - 1) / 2;
if (heap[index, 0] < heap[parent, 0]) {
Swap(index, parent);
index = parent;
}
else
break;
}
}
// Maintain heap property (heapify down)
private void HeapifyDown(int index)
{
while (true) {
int left = 2 * index + 1;
int right = 2 * index + 2;
int smallest = index;
if (left < size
&& heap[left, 0] < heap[smallest, 0])
smallest = left;
if (right < size
&& heap[right, 0] < heap[smallest, 0])
smallest = right;
if (smallest != index) {
Swap(index, smallest);
index = smallest;
}
else
break;
}
}
// Swap elements in heap
private void Swap(int i, int j)
{
int tempFinish = heap[i, 0];
int tempStart = heap[i, 1];
heap[i, 0] = heap[j, 0];
heap[i, 1] = heap[j, 1];
heap[j, 0] = tempFinish;
heap[j, 1] = tempStart;
}
}
// Function to solve the Activity Selection Problem
// using a Priority Queue (Min-Heap)
static int activitySelection(int[] start, int[] finish)
{
int n = start.Length;
int ans = 0;
// Create Min-Heap (acts as Priority Queue)
MinHeap heap = new MinHeap(n);
// Insert all activities into the Min-Heap
for (int i = 0; i < n; i++) {
heap.Insert(finish[i], start[i]);
}
// Variable to track last selected activity's finish
// time
int finishtime = -1;
// Process activities in increasing order of finish
// time
while (!heap.IsEmpty()) {
(int finishTime, int startTime)
= heap.ExtractMin();
if (startTime > finishtime) {
finishtime = finishTime;
ans++;
}
}
return ans;
}
static void Main()
{
int[] start = { 1, 3, 0, 5, 8, 5 };
int[] finish = { 2, 4, 6, 7, 9, 9 };
Console.WriteLine(activitySelection(start, finish));
}
}
class MinHeap {
constructor() { this.heap = []; }
// Insert an element into the heap
push(val)
{
this.heap.push(val);
this.heapifyUp();
}
// Remove and return the smallest element
pop()
{
if (this.heap.length === 1)
return this.heap.pop();
const min = this.heap[0];
this.heap[0] = this.heap.pop();
this.heapifyDown();
return min;
}
// Heapify upwards (to maintain heap property after
// insertion)
heapifyUp()
{
let index = this.heap.length - 1;
while (index > 0) {
let parentIndex = Math.floor((index - 1) / 2);
if (this.heap[parentIndex][0]
<= this.heap[index][0])
break;
[this.heap[parentIndex], this.heap[index]] = [
this.heap[index], this.heap[parentIndex]
];
index = parentIndex;
}
}
// Heapify downwards (to maintain heap property after
// removal)
heapifyDown()
{
let index = 0;
while (true) {
let left = 2 * index + 1;
let right = 2 * index + 2;
let smallest = index;
if (left < this.heap.length
&& this.heap[left][0]
< this.heap[smallest][0]) {
smallest = left;
}
if (right < this.heap.length
&& this.heap[right][0]
< this.heap[smallest][0]) {
smallest = right;
}
if (smallest === index)
break;
[this.heap[index], this.heap[smallest]] =
[ this.heap[smallest], this.heap[index] ];
index = smallest;
}
}
size() { return this.heap.length; }
}
// Function to solve the activity selection problem
function activitySelection(start, finish)
{
let ans = 0;
let minHeap = new MinHeap();
// Insert all activities into the min heap
for (let i = 0; i < start.length; i++) {
minHeap.push([ finish[i], start[i] ]);
}
let finishtime = -1;
// Process activities in order of finish time
while (minHeap.size() > 0) {
let activity
= minHeap.pop(); // Get the activity with the
// smallest finish time
if (activity[1] > finishtime) {
finishtime = activity[0];
ans++;
}
}
return ans;
}
// Example usage
let start = [ 1, 3, 0, 5, 8, 5 ];
let finish = [ 2, 4, 6, 7, 9, 9 ];
console.log(activitySelection(start, finish)); // Output: 4
Output
4