Topics › Greedy Algorithms › Medium/Hard
N meetings in one room
Java source ↗ Edit this article ↗
Jump to code:
Problem
There is one meeting room and n meeting requests. Meeting i (numbered from 1) runs from start[i] to end[i]. Only one meeting can be in the room at a time, and a chosen meeting must start strictly after the previous chosen meeting ends: if one meeting ends at time 20, the next may not start at 20. Find the maximum number of meetings that can be held, and list the chosen meetings in the order they take place.
This is the classic activity selection problem. The GeeksforGeeks version only asks for the count; Striver’s version also prints the order, so the optimal method here returns the list and its size is the count.
Constraints: 1 ≤ n ≤ 10^5, 0 ≤ start[i] ≤ end[i] ≤ 10^6.
Example 1
start = [1, 3, 0, 5, 8, 5], end = [2, 4, 5, 7, 9, 9]
answer = 4, order 1 2 4 5 // (1,2) (3,4) (5,7) (8,9) never overlap
Example 2
start = [10, 12, 20], end = [20, 25, 30]
answer = 1, order 1 // meeting 3 starts at 20, the same moment meeting 1 ends, which is a clash
Approach 1: Brute force, every subset
Intuition. The answer is the largest set of meetings in which no two overlap, so try every set. A set is valid if, after sorting its meetings by start time, each meeting starts strictly after the previous one ends. Because every meeting satisfies start ≤ end, checking neighbours in start order is enough: if each consecutive pair is clean, the meetings form a chain and no two can overlap.
Algorithm.
- For every bitmask from
0to2^n - 1, collect the meetings whose bits are set. - Skip the set if it is not larger than the best found so far.
- Sort the set by start time and check
start[next] > end[previous]for every consecutive pair. - If the set is valid, record its size.
static int bruteForce(int[] start, int[] end) {
int n = start.length, best = 0;
for (int mask = 0; mask < (1 << n); mask++) {
List<Integer> chosen = new ArrayList<>();
for (int i = 0; i < n; i++) if ((mask & (1 << i)) != 0) chosen.add(i);
if (chosen.size() <= best) continue; // cannot improve the answer
chosen.sort((a, b) -> Integer.compare(start[a], start[b]));
boolean ok = true;
for (int k = 1; k < chosen.size() && ok; k++) {
if (start[chosen.get(k)] <= end[chosen.get(k - 1)]) ok = false; // clash with the previous meeting
}
if (ok) best = chosen.size();
}
return best;
}
Complexity. Time O(2^n · n log n): 2^n subsets, each sorted and scanned. Space O(n) for the current subset. Only feasible for about 20 meetings; it serves as a correctness reference.
Approach 2: Optimal, earliest finishing meeting first
Intuition. Which meeting should go first? Not the earliest starting one: a meeting (0, 100) starts first and blocks everything. Not the shortest one either: with (0, 5), (6, 10) and a short (4, 7), picking the short meeting kills both others. The right choice is the meeting that ends earliest, because it leaves the room free as soon as possible for whatever comes next.
Why this is safe (the exchange argument): let g be the meeting with the smallest end time. Take any optimal schedule and let f be its first meeting. Then end[g] ≤ end[f]. Replace f with g: g finishes no later than f did, so it still finishes before the schedule’s second meeting starts. The new schedule has the same size and starts with g. After committing to g, what remains is the same problem restricted to meetings that start after end[g], and the same argument applies again. So repeatedly taking the earliest-ending compatible meeting is optimal.
In code this becomes a single sorted sweep: sort meetings by end time and take each one whose start is strictly after the end of the last one taken.
Algorithm.
- Sort meeting indices by end time; on equal end times, put the lower meeting number first so the printed order is deterministic.
- Set
lastEndto a value smaller than any time. - For each meeting in that order, if
start[i] > lastEnd, take it (recordi + 1) and setlastEnd = end[i]. - Return the list of taken meetings; its size is the answer.
static List<Integer> optimal(int[] start, int[] end) {
int n = start.length;
Integer[] order = new Integer[n];
for (int i = 0; i < n; i++) order[i] = i;
// earliest finish first; on equal end times the lower meeting number goes first
Arrays.sort(order, (a, b) -> end[a] != end[b] ? Integer.compare(end[a], end[b]) : Integer.compare(a, b));
List<Integer> picked = new ArrayList<>();
long lastEnd = Long.MIN_VALUE;
for (int i : order) {
if (start[i] > lastEnd) {
picked.add(i + 1); // meetings are numbered from 1
lastEnd = end[i];
}
}
return picked;
}
lastEnd is a long initialised to Long.MIN_VALUE, so the very first meeting is always accepted without a special case, even if a meeting could start at time 0 or a negative time. Comparing an int start with a long is a normal widening comparison.
Dry run on Example 1. Sorted by end time the meetings are 1 (1,2), 2 (3,4), 3 (0,5), 4 (5,7), 5 (8,9), 6 (5,9).
| meeting | start | end | start > lastEnd? | lastEnd after |
|---|---|---|---|---|
| 1 | 1 | 2 | yes, nothing chosen yet | 2 |
| 2 | 3 | 4 | 3 > 2, yes | 4 |
| 3 | 0 | 5 | 0 > 4, no | 4 |
| 4 | 5 | 7 | 5 > 4, yes | 7 |
| 5 | 8 | 9 | 8 > 7, yes | 9 |
| 6 | 5 | 9 | 5 > 9, no | 9 |
The chosen order is 1 2 4 5, so the answer is 4.
Complexity. Time O(n log n) for sorting; the sweep is O(n). Space O(n) for the index array and the result list.
Edge cases and pitfalls
- Strict versus non-strict. This problem forbids a meeting starting at the exact moment another ends, so the check is
start > lastEnd. LeetCode-style interval problems often allow touching intervals; there you would use>=. Withstart = [1, 2, 3],end = [2, 3, 4]the answer here is2, not3. - Sorting by the wrong key. Sorting by start time or by duration gives wrong answers on the counterexamples above. Only end time works.
- Comparator overflow. Write
Integer.compare(end[a], end[b]), notend[a] - end[b]; subtraction can overflow for extreme values. - Losing meeting numbers. Sorting
startandendseparately destroys the pairing. Sort an index array (or an array of objects) instead, and add1when reporting since meetings are numbered from1. - Identical meetings. Only one of several identical meetings can be chosen; the tie-break picks the lowest number.
- No meetings. The sweep does nothing and the answer is
0with an empty order.
Related problems
- Non-overlapping Intervals is the same greedy seen from the other side: remove the fewest intervals.
- Minimum number of platforms required for a railway asks how many rooms you need to host every meeting.
- Job sequencing Problem is a scheduling problem where jobs carry profits and deadlines.
- Meeting Rooms asks whether one person can attend every meeting.