AlgoAtlas

Intervals

Phase 2 7 solutions

Intervals

mindmap
  root((Intervals))
    Merge/Insert
      Merge Intervals LC56
      Insert Interval LC57
    Scheduling Rooms
      Meeting Rooms LC252
      Meeting Rooms II LC253
      Meeting Rooms III LC2402
    Sweep Line
      Non-Overlapping Intervals LC435
    Query-Based
      Minimum Interval to Include Each Query LC1851

Problem List

# Problem LC # Difficulty Key Technique
1 Insert Interval 57 Medium Linear scan + merge
2 Merge Intervals 56 Medium Sort + merge
3 Non-Overlapping Intervals 435 Medium Greedy scheduling
4 Meeting Rooms 252 Easy Sort + check overlap
5 Meeting Rooms II 253 Medium Min-heap
6 Meeting Rooms III 2402 Hard Two heaps
7 Minimum Interval to Include Each Query 1851 Hard Sort + heap + offline queries

Interval Problem Decision Tree

flowchart TD
    A[Interval Problem] --> B{What is asked?}
    B --> C["Merge / Insert"]
    B --> D["Count rooms / resources"]
    B --> E[Remove minimum intervals]
    B --> F[Answer queries]

    C --> C1[Sort by start, linear merge]
    C --> C2[Insert Interval #57 — binary search + merge]

    D --> D1[Sort by start, min-heap on end times]
    D1 --> D2[Meeting Rooms II #253]
    D1 --> D3[Meeting Rooms III #2402 — two heaps]

    E --> E1[Sort by end, greedy keep earliest end]
    E1 --> E2[Non-Overlapping Intervals #435]

    F --> F1[Offline: sort queries + sweep with heap]
    F1 --> F2[Min Interval per Query #1851]

Merge Intervals Algorithm Flow

flowchart TD
    A[Sort intervals by start time] --> B[result = first interval]
    B --> C[For each remaining interval]
    C --> D{"current.start <= last.end?"}
    D -- Yes --> E[Merge: last.end = max of last.end and current.end]
    D -- No --> F[Append current to result as new interval]
    E --> G{More intervals?}
    F --> G
    G -- Yes --> C
    G -- No --> H[Return result]

    style A fill:#2d6a4f,color:#fff
    style H fill:#1b4332,color:#fff

Sweep Line / Event-Based Approach

flowchart TD
    A[For each interval, emit start +1 and end -1 events] --> B[Sort all events by time]
    B --> C[active = 0, maxActive = 0]
    C --> D[For each event in order]
    D --> E[active += event.delta]
    E --> F[maxActive = max of maxActive and active]
    F --> G{More events?}
    G -- Yes --> D
    G -- No --> H[Return maxActive]

    style A fill:#2d6a4f,color:#fff
    style H fill:#1b4332,color:#fff

Used for: counting max concurrent meetings (Meeting Rooms II alternative)


Meeting Rooms Heap Approach

flowchart TD
    A[Sort meetings by start time] --> B[min-heap = empty, rooms = 0]
    B --> C[For each meeting start, end]
    C --> D{"heap not empty AND heap.top <= start?"}
    D -- Yes --> E[Pop earliest ending room — reuse it]
    D -- No --> F[No room free — allocate new room]
    E --> G[Push end time to heap]
    F --> G
    G --> H{More meetings?}
    H -- Yes --> C
    H -- No --> I[Return heap size = rooms needed]

    style A fill:#2d6a4f,color:#fff
    style I fill:#1b4332,color:#fff

Interval Overlap Detection Logic

flowchart TD
    A[Two intervals A = a1,a2 and B = b1,b2] --> B{Do they overlap?}
    B --> C["Overlap condition: a1 <= b2 AND b1 <= a2"]
    C --> D{True?}
    D -- Yes --> E[Overlapping — merged = min a1 b1, max a2 b2]
    D -- No --> F[Non-overlapping — keep separate]

    E --> G[Use for: Merge Intervals, Insert Interval]
    F --> H[Use for: Non-Overlapping — count removals]

    style E fill:#2d6a4f,color:#fff
    style F fill:#9b2226,color:#fff

Complexity Summary

Problem Time Space
Insert Interval O(n) O(n)
Merge Intervals O(n log n) O(n)
Non-Overlapping Intervals O(n log n) O(1)
Meeting Rooms O(n log n) O(1)
Meeting Rooms II O(n log n) O(n)
Meeting Rooms III O(n log n) O(n)
Min Interval per Query O((n+q) log n) O(n+q)

Study Order

  1. Meeting Rooms — basic overlap check
  2. Merge Intervals — core merge pattern
  3. Insert Interval — merge with binary search
  4. Non-Overlapping Intervals — greedy scheduling
  5. Meeting Rooms II — heap for resource counting
  6. Meeting Rooms III — two-heap advanced
  7. Minimum Interval per Query — offline + heap

Intervals

Core Intuition

Interval problems reduce to: sort by start (or end), then decide how to merge, count, or schedule. The key insight is that after sorting, you only need to compare the current interval with the last processed one.

  • Merge: Sort by start; merge if current.start ≤ last.end
  • Non-overlapping / scheduling: Sort by end; greedy keep non-overlapping
  • Meeting rooms (count): Sort starts and ends separately (sweep line) or use a min-heap
  • Query problems: Offline sort + heap

Decision Flowchart

flowchart TD
    A[Interval Problem] --> B{"What's the goal?"}
    B -->|Combine overlapping intervals| C["Sort by start<br/>Merge Intervals"]
    B -->|Insert into sorted list| D["Binary search + merge<br/>Insert Interval"]
    B -->|Remove min intervals to make non-overlapping| E["Sort by end<br/>greedy keep<br/>Non-Overlapping"]
    B -->|Can one person attend all?| F["Sort by start<br/>check overlap<br/>Meeting Rooms I"]
    B -->|Min rooms needed| G{Method preference?}
    G -->|Sweep line| H["Sort starts and ends<br/>two-pointer<br/>Meeting Rooms II"]
    G -->|Heap| I["Min-heap of end times<br/>Meeting Rooms II/III"]
    B -->|Answer range queries| J["Sort queries + intervals<br/>min-heap<br/>Min Interval Query"]

Problems

Insert Interval — LC 57 | Medium

  • Learn: Three phases: add all intervals ending before new one, merge overlapping, add remaining
  • Mistake: Forgetting to handle the case where new interval doesn't overlap anything
  • Complexity: O(n) time, O(n) space
  • Edge: Empty intervals list; new interval before all; new interval after all; complete overlap

Merge Intervals — LC 56 | Medium

  • Learn: Sort by start; if current.start ≤ last.end, extend last.end = max(last.end, current.end)
  • Mistake: Not taking max of end times (one interval may be fully contained in another)
  • Complexity: O(n log n) time, O(n) space
  • Edge: Single interval; all intervals overlap into one; no overlaps at all

Non-Overlapping Intervals — LC 435 | Medium

  • Learn: Sort by end time; greedily keep intervals with earliest end; count removals
  • Mistake: Sorting by start instead of end; counting kept instead of removed
  • Complexity: O(n log n) time, O(1) space
  • Edge: All overlapping → remove n-1; no overlaps → remove 0

Meeting Rooms — LC 252 | Easy

  • Learn: Sort by start; check if any interval starts before previous ends
  • Mistake: Not sorting first
  • Complexity: O(n log n) time, O(1) space
  • Edge: Single meeting → true; empty → true

Meeting Rooms II — LC 253 | Medium

  • Learn: Min-heap of end times; if earliest end ≤ current start, reuse room (pop+push); else add room
  • Mistake: Sweep line approach: sort starts and ends separately, two pointers, increment rooms when start < end
  • Complexity: O(n log n) time, O(n) space
  • Edge: All meetings at same time → n rooms; sequential meetings → 1 room

Meeting Rooms III — LC 2402 | Hard

  • Learn: Two heaps: available rooms (min-heap by room number), occupied rooms (min-heap by end time); assign to earliest-ending room when none available
  • Mistake: Not tracking which room number is used (need to count per room); not handling tie-breaking (use lowest room number)
  • Complexity: O(n log n + m log n) time where m=meetings, O(n) space
  • Edge: More meetings than rooms; meetings with same start time (process in order)

Minimum Interval to Include Each Query — LC 1851 | Hard

  • Learn: Sort intervals by size; sort queries with original indices; sweep with min-heap (size, end); remove expired intervals
  • Mistake: Not sorting queries offline; not removing intervals whose end < query
  • Complexity: O((n+q) log n) time, O(n+q) space
  • Edge: Query not covered by any interval → -1; multiple intervals covering same query → smallest size

General Edge Cases

  • Empty interval list
  • Single interval
  • Intervals with same start or end time
  • Fully contained intervals (one inside another)
  • Queries outside all interval ranges
  • Integer overflow when computing interval sizes
  • Intervals given in unsorted order (always sort first)
# Problem LC # Difficulty Key Technique
1 Insert Interval 57 Medium Linear scan + merge
2 Merge Intervals 56 Medium Sort + merge
3 Non-Overlapping Intervals 435 Medium Greedy scheduling
4 Meeting Rooms 252 Easy Sort + check overlap
5 Meeting Rooms II 253 Medium Min-heap
6 Meeting Rooms III 2402 Hard Two heaps
7 Minimum Interval to Include Each Query 1851 Hard Sort + heap + offline queries
Insert Interval ▼ expand
from typing import List


class Solution:
    """
    ## 57. Insert Interval

    Given a list of non-overlapping intervals sorted by start time, insert a new interval
    and merge if necessary. Return the resulting list of non-overlapping intervals.

    ### Example
    ```text
    Input:  intervals = [[1,3],[6,9]], newInterval = [2,5]
    Output: [[1,5],[6,9]]

    Input:  intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]
    Output: [[1,2],[3,10],[12,16]]
    ```

    ### Constraints
    - 0 <= intervals.length <= 10^4
    - intervals is sorted in ascending order by start
    - intervals[i].length == newInterval.length == 2
    - 0 <= intervals[i][0] <= intervals[i][1] <= 10^5
    """

    def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
        # Add all intervals ending before new interval starts
        # Merge all overlapping intervals with the new one
        # Add remaining intervals that start after the merged interval ends
        # O(n) time, O(n) space — linear scan and merge
        result = []
        i, n = 0, len(intervals)
        # Add all intervals that end before newInterval starts
        while i < n and intervals[i][1] < newInterval[0]:
            result.append(intervals[i])
            i += 1
        # Merge all overlapping intervals
        while i < n and intervals[i][0] <= newInterval[1]:
            newInterval[0] = min(newInterval[0], intervals[i][0])
            newInterval[1] = max(newInterval[1], intervals[i][1])
            i += 1
        result.append(newInterval)
        # Add remaining intervals
        result.extend(intervals[i:])
        return result


if __name__ == '__main__':
    s = Solution()
    assert s.insert([[1, 3], [6, 9]], [2, 5]) == [[1, 5], [6, 9]]
    assert s.insert([[1, 2], [3, 5], [6, 7], [8, 10], [12, 16]], [4, 8]) == [[1, 2], [3, 10], [12, 16]]
    assert s.insert([], [5, 7]) == [[5, 7]]
    assert s.insert([[1, 5]], [2, 3]) == [[1, 5]]
    print("All tests passed.")
Meeting Rooms ▼ expand
from typing import List


class Solution:
    """
    ## 252. Meeting Rooms

    Given an array of meeting time intervals where intervals[i] = [start_i, end_i],
    return true if a person could attend all meetings (no two meetings overlap).

    ### Example
    ```text
    Input:  intervals = [[0,30],[5,10],[15,20]]
    Output: false

    Input:  intervals = [[7,10],[2,4]]
    Output: true
    ```

    ### Constraints
    - 0 <= intervals.length <= 10^4
    - intervals[i].length == 2
    - 0 <= start_i < end_i <= 10^6
    """

    def canAttendMeetings(self, intervals: List[List[int]]) -> bool:
        # Sort by start; if any meeting starts before previous ends, conflict
        # O(n log n) time, O(1) space — sort by start and check adjacent overlap
        intervals.sort(key=lambda x: x[0])
        for i in range(1, len(intervals)):
            if intervals[i][0] < intervals[i - 1][1]:
                return False
        return True


if __name__ == '__main__':
    s = Solution()
    assert s.canAttendMeetings([[0, 30], [5, 10], [15, 20]]) == False
    assert s.canAttendMeetings([[7, 10], [2, 4]]) == True
    assert s.canAttendMeetings([]) == True
    assert s.canAttendMeetings([[1, 5], [5, 10]]) == True
    print("All tests passed.")
Meeting Rooms Ii ▼ expand
from typing import List
import heapq


class Solution:
    """
    ## 253. Meeting Rooms II

    Given an array of meeting time intervals, return the minimum number of conference rooms required.

    ### Example
    ```text
    Input:  intervals = [[0,30],[5,10],[15,20]]
    Output: 2

    Input:  intervals = [[7,10],[2,4]]
    Output: 1
    ```

    ### Constraints
    - 1 <= intervals.length <= 10^4
    - 0 <= start_i < end_i <= 10^6
    """

    def minMeetingRooms_heap(self, intervals: List[List[int]]) -> int:
        # O(n log n) time, O(n) space — sort + min heap of end times
        intervals.sort(key=lambda x: x[0])
        heap = []  # min-heap of end times
        for start, end in intervals:
            if heap and heap[0] <= start:
                heapq.heapreplace(heap, end)
            else:
                heapq.heappush(heap, end)
        return len(heap)

    def minMeetingRooms(self, intervals: List[List[int]]) -> int:
        # Min-heap of end times: if next meeting starts after earliest end, reuse room
        # Otherwise allocate a new room; heap size = rooms needed
        # O(n log n) time, O(n) space — chronological ordering with start/end events
        starts = sorted(s for s, _ in intervals)
        ends = sorted(e for _, e in intervals)
        rooms = end_ptr = 0
        for start in starts:
            if start < ends[end_ptr]:
                rooms += 1
            else:
                end_ptr += 1
        return rooms


if __name__ == '__main__':
    s = Solution()
    assert s.minMeetingRooms([[0, 30], [5, 10], [15, 20]]) == 2
    assert s.minMeetingRooms([[7, 10], [2, 4]]) == 1
    assert s.minMeetingRooms([[1, 5], [2, 6], [3, 7]]) == 3
    assert s.minMeetingRooms_heap([[0, 30], [5, 10], [15, 20]]) == 2
    print("All tests passed.")
Meeting Rooms Iii ▼ expand
from typing import List
import heapq


class Solution:
    """
    ## 2402. Meeting Rooms III

    You have n rooms numbered 0 to n-1. Meetings are given as [start, end] (half-open).
    Each meeting is assigned to the lowest-numbered available room. If no room is free,
    the meeting is delayed until the earliest room becomes free (duration preserved).
    Return the room that held the most meetings (lowest index on tie).

    ### Example
    ```text
    Input:  n = 2, meetings = [[0,10],[1,5],[2,7],[3,4]]
    Output: 0

    Input:  n = 3, meetings = [[1,20],[2,10],[3,5],[4,9],[6,8]]
    Output: 1
    ```

    ### Constraints
    - 1 <= n <= 100
    - 1 <= meetings.length <= 10^5
    - 0 <= start_i < end_i <= 5 * 10^5
    """

    def mostBooked(self, n: int, meetings: List[List[int]]) -> int:
        # Two heaps: available rooms (min-heap of room indices) and busy rooms (end_time, room)
        # Free up rooms whose end_time <= current meeting start before assigning
        # If no room available, wait for earliest-ending room; track usage count per room
        # O(m log n) time, O(n) space — two heaps: available rooms and ongoing meetings
        meetings.sort()
        available = list(range(n))          # min-heap of free room indices
        ongoing = []                         # min-heap of (end_time, room_index)
        count = [0] * n

        for start, end in meetings:
            # Free up rooms that have finished
            while ongoing and ongoing[0][0] <= start:
                _, room = heapq.heappop(ongoing)
                heapq.heappush(available, room)

            if available:
                room = heapq.heappop(available)
                heapq.heappush(ongoing, (end, room))
            else:
                # Delay: use the room that finishes earliest
                finish, room = heapq.heappop(ongoing)
                heapq.heappush(ongoing, (finish + (end - start), room))

            count[room] += 1

        return count.index(max(count))


if __name__ == '__main__':
    s = Solution()
    assert s.mostBooked(2, [[0, 10], [1, 5], [2, 7], [3, 4]]) == 0
    assert s.mostBooked(3, [[1, 20], [2, 10], [3, 5], [4, 9], [6, 8]]) == 1
    assert s.mostBooked(1, [[0, 1], [1, 2], [2, 3]]) == 0
    print("All tests passed.")
Merge Intervals ▼ expand
from typing import List


class Solution:
    """
    ## 56. Merge Intervals

    Given an array of intervals, merge all overlapping intervals and return an array
    of the non-overlapping intervals that cover all the intervals in the input.

    ### Example
    ```text
    Input:  intervals = [[1,3],[2,6],[8,10],[15,18]]
    Output: [[1,6],[8,10],[15,18]]

    Input:  intervals = [[1,4],[4,5]]
    Output: [[1,5]]
    ```

    ### Constraints
    - 1 <= intervals.length <= 10^4
    - intervals[i].length == 2
    - 0 <= intervals[i][0] <= intervals[i][1] <= 10^4
    """

    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        # Sort by start time; merge current with next if they overlap
        # Two intervals overlap if next.start <= current.end
        # O(n log n) time, O(n) space — sort by start then merge
        intervals.sort(key=lambda x: x[0])
        result = [intervals[0]]
        for start, end in intervals[1:]:
            if start <= result[-1][1]:
                result[-1][1] = max(result[-1][1], end)
            else:
                result.append([start, end])
        return result


if __name__ == '__main__':
    s = Solution()
    assert s.merge([[1, 3], [2, 6], [8, 10], [15, 18]]) == [[1, 6], [8, 10], [15, 18]]
    assert s.merge([[1, 4], [4, 5]]) == [[1, 5]]
    assert s.merge([[1, 4], [0, 4]]) == [[0, 4]]
    assert s.merge([[1, 4], [2, 3]]) == [[1, 4]]
    print("All tests passed.")
Minimum Interval To Include Each Query ▼ expand
from typing import List
import heapq


class Solution:
    """
    ## 1851. Minimum Interval to Include Each Query

    You are given a 2D integer array intervals and an integer array queries.
    For each query, find the size (end - start + 1) of the smallest interval containing
    the query point. Return -1 if no such interval exists.

    ### Example
    ```text
    Input:  intervals = [[1,4],[2,4],[3,6],[4,4]], queries = [2,3,4,5]
    Output: [3,3,1,4]
    (query 2: [2,4] size 3; query 3: [2,4] size 3; query 4: [4,4] size 1; query 5: [3,6] size 4)

    Input:  intervals = [[2,3],[2,5],[1,8],[20,25]], queries = [2,19,5,22]
    Output: [2,-1,4,6]
    ```

    ### Constraints
    - 1 <= intervals.length <= 10^5
    - 1 <= queries.length <= 10^5
    - 1 <= intervals[i][0] <= intervals[i][1] <= 10^7
    - 1 <= queries[i] <= 10^7
    """

    def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:
        # Sort intervals by length; sort queries with original indices
        # Sweep queries in order; add intervals that start <= query to a min-heap by size
        # Pop intervals from heap that end < query; top of heap is the answer
        # O((n + q) log n) time, O(n + q) space — sort intervals and queries, sweep with min heap
        intervals.sort()
        sorted_queries = sorted(enumerate(queries), key=lambda x: x[1])
        result = [-1] * len(queries)
        heap = []  # (size, end) — min heap by interval size
        i = 0

        for idx, q in sorted_queries:
            # Push all intervals whose start <= q
            while i < len(intervals) and intervals[i][0] <= q:
                start, end = intervals[i]
                heapq.heappush(heap, (end - start + 1, end))
                i += 1
            # Remove intervals that have ended before q
            while heap and heap[0][1] < q:
                heapq.heappop(heap)
            if heap:
                result[idx] = heap[0][0]

        return result


if __name__ == '__main__':
    s = Solution()
    assert s.minInterval([[1, 4], [2, 4], [3, 6], [4, 4]], [2, 3, 4, 5]) == [3, 3, 1, 4]
    assert s.minInterval([[2, 3], [2, 5], [1, 8], [20, 25]], [2, 19, 5, 22]) == [2, -1, 4, 6]
    print("All tests passed.")
Non Overlapping Intervals ▼ expand
from typing import List


class Solution:
    """
    ## 435. Non-overlapping Intervals

    Given an array of intervals, return the minimum number of intervals you need to remove
    to make the rest of the intervals non-overlapping.

    ### Example
    ```text
    Input:  intervals = [[1,2],[2,3],[3,4],[1,3]]
    Output: 1  (remove [1,3])

    Input:  intervals = [[1,2],[1,2],[1,2]]
    Output: 2

    Input:  intervals = [[1,2],[2,3]]
    Output: 0
    ```

    ### Constraints
    - 1 <= intervals.length <= 10^5
    - intervals[i].length == 2
    - -5 * 10^4 <= intervals[i][0] < intervals[i][1] <= 5 * 10^4
    """

    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        # Sort by end time; greedily keep intervals with earliest end
        # Count intervals that must be removed (overlap with last kept)
        # O(n log n) time, O(1) space — sort by end, greedily keep non-overlapping
        intervals.sort(key=lambda x: x[1])
        removed = 0
        prev_end = float('-inf')
        for start, end in intervals:
            if start >= prev_end:
                prev_end = end
            else:
                removed += 1
        return removed


if __name__ == '__main__':
    s = Solution()
    assert s.eraseOverlapIntervals([[1, 2], [2, 3], [3, 4], [1, 3]]) == 1
    assert s.eraseOverlapIntervals([[1, 2], [1, 2], [1, 2]]) == 2
    assert s.eraseOverlapIntervals([[1, 2], [2, 3]]) == 0
    print("All tests passed.")

Merge Intervals

Sort by start, merge overlapping intervals greedily

012345678910111213141516171819[1,3][2,6][8,10][15,18]merged:
Intervals sorted by start. Press "Next Step" to merge.