Difficulty: Easy Pattern: Intervals LeetCode: https://leetcode.com/problems/meeting-rooms/
Given an array of meeting time intervals where intervals[i] = [start_i,
end_i], determine whether a person could attend all meetings. The
person cannot attend two meetings that overlap. Meetings that only touch
at a point (one ends exactly when the next starts) are fine — back-to-back
is allowed.
Signature:
boolean canAttendMeetings(int[][] intervals)
Examples (verbatim from LeetCode):
Input: intervals = [[0,30],[5,10],[15,20]]
Output: false
Explanation: [0,30] overlaps both [5,10] and [15,20].
Input: intervals = [[7,10],[2,4]]
Output: true
The trigger signals are pure Intervals: a list of [start, end] pairs and
the word “overlap”. We do not need to merge or count anything — we only
need to say yes or no to “does any pair overlap?”.
The brute force compares every pair, O(n^2). The intervals pattern
collapses it to sort by start, then check each consecutive pair. Why
is checking only neighbours enough? Once the list is start-sorted, any
interval that overlaps another must overlap the interval immediately
before it: an overlap with a later interval j requires intervals[j].start <
intervals[i].end for some earlier i, and the start-sorted order means
the largest end among intervals before j is held by j-1 (or one even
closer). So if no consecutive pair overlaps, no pair at all overlaps.
One subtlety defines this problem: the comparison is strict <. A
meeting ending at 2 and one starting at 2 are compatible, so
intervals[i].start < intervals[i-1].end flags an overlap only when the
next meeting starts strictly before the previous one ends. This is the
opposite of Merge Intervals, where touching ranges merge. Read the
problem’s overlap definition before picking < versus <=.
Pause and answer before expanding. A wrong first guess teaches more than a fast right one.
Q1 (recall). After sorting the meetings by start, which pairs do we need to compare to detect any overlap?
Q2 (comprehend). The overlap test is strict: intervals[i][0] < intervals[i-1][1]. Why strict < instead of <=?
<= would wrongly flag it as a conflict<= would miss real overlapsfunction canAttendMeetings(intervals):
sort intervals by start (ascending)
for i from 1 to length - 1:
if intervals[i].start < intervals[i-1].end:
return false # this pair overlaps -> cannot attend both
return true
The loop does not even need an early-exit variable: the first overlap
short-circuits to false; reaching the end means every adjacent pair is
compatible, which (by the sort) means every pair is.
import java.util.*;
class Solution {
public boolean canAttendMeetings(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
for (int i = 1; i < intervals.length; i++) {
// Strict <: a meeting starting exactly when the previous ends is OK.
if (intervals[i][0] < intervals[i - 1][1]) {
return false;
}
}
return true;
}
}
Arrays.sort orders by start, which makes the overlap check local to
consecutive pairs. We start the loop at i = 1 and compare intervals[i]
against intervals[i - 1], so every adjacent pair is examined exactly
once. The strict < (rather than <=) is what makes back-to-back
meetings legal — the single most important detail in this problem. With
zero or one meetings the loop body never runs and we return true, which
is correct: nothing to conflict with.
Time: O(n log n) -- the sort dominates; the single pass is O(n).
Space: O(log n) -- in-place sort's recursion stack. No extra structures.
Step-by-step on intervals = [[0,30],[5,10],[15,20]] (already start-sorted):
| i | intervals[i] | intervals[i-1] | intervals[i][0] < intervals[i-1][1]? | result |
|---|---|---|---|---|
| 1 | [5,10] |
[0,30] |
5 < 30 yes | false |
Return false at i = 1. The meeting [5,10] starts at 5, which is
strictly before [0,30] ends at 30 — a clear overlap. We never even look
at [15,20], because one conflict is enough to answer.
For contrast, on intervals = [[7,10],[2,4]]:
First sort by start: [[2,4],[7,10]].
| i | intervals[i] | intervals[i-1] | 7 < … no wait, 7 < 4? | result |
|---|---|---|---|---|
| 1 | [7,10] |
[2,4] |
7 < 4 no | — |
Loop completes with no early return, so the final return true fires.
The two meetings are compatible: [2,4] ends at 4, [7,10] starts at 7,
plenty of gap.
Touching case [[1,2],[2,3]]:
| i | intervals[i] | intervals[i-1] | 2 < 2? | result |
|---|---|---|---|---|
| 1 | [2,3] |
[1,2] |
no | — |
Loop completes, return true — the back-to-back meetings are attendable.
A non-strict <= here would wrongly flag an overlap.
Q1 (apply). Trace intervals = [[1,5],[3,4],[6,7]] after sorting by start. What is returned?
truefalseQ2 (analyze). What does canAttendMeetings([]) (empty input) return, and why does the code handle it with no special case?
true – the loop never runs, so control falls through to return true; with zero meetings nothing can conflictfalse – the empty input is specialQ3 (transfer). Suppose the question changed to “what is the MINIMUM number of meetings you must cancel so the rest do not overlap?” How does the approach change?
<= instead of <. Touching meetings are legal, so
intervals[i][0] <= intervals[i-1][1] reports a false conflict on
back-to-back meetings like [1,2] then [2,3]. This is the
problem-defining detail.[[2,4],[7,10],[0,3]], where [0,3] overlaps
[2,4]) are never compared, and the function wrongly returns true.intervals[i][0] vs intervals[i-1][1]). Mixing up indices —
comparing start to start, or end to end — never detects overlap.true (all meetings attendable), not false. The early return is the
false case; the loop falling through is the true case.