Facebook Pixel

1942. The Number of the Smallest Unoccupied Chair

Problem Description

You have a party with n friends numbered from 0 to n - 1. The party venue has an infinite number of chairs numbered from 0 to infinity.

When a friend arrives at the party, they follow this rule: they sit on the unoccupied chair with the smallest number available. For example, if chairs 0, 1, and 5 are already occupied when a friend arrives, they will sit on chair 2 (the smallest unoccupied chair).

When a friend leaves the party, their chair immediately becomes available. If another friend arrives at the exact same moment someone leaves, the arriving friend can take that just-vacated chair.

You are given:

  • A 2D array times where times[i] = [arrival_i, leaving_i] represents when the ith friend arrives and leaves
  • An integer targetFriend indicating which friend you need to track
  • All arrival times are distinct (no two friends arrive at the same time)

Your task is to determine which chair number the friend numbered targetFriend will sit on when they arrive.

The solution uses two min-heaps to efficiently track available and occupied chairs. The idle heap maintains available chair numbers in ascending order, while the busy heap tracks when chairs will become available again, sorted by leaving time. By processing friends in order of arrival time and updating chair availability accordingly, we can determine exactly which chair the target friend will occupy.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we need to simulate the chair assignment process in chronological order of arrivals. Since friends always take the smallest available chair number, we need an efficient way to track which chairs are available at any given time.

Think about what happens at each arrival:

  1. Some friends might have left, freeing up their chairs
  2. The arriving friend needs the smallest available chair

This naturally leads to using a min-heap for available chairs - it gives us the smallest chair number in O(log n) time. But how do we know which chairs become free?

We realize that chairs become available based on leaving times. When a friend arrives, any friend who was supposed to leave before or at this arrival time will have freed their chair. This suggests we need another data structure to track occupied chairs by their "expiration time" (leaving time).

A second min-heap ordered by leaving time perfectly fits this need. At each arrival, we can efficiently check and remove all friends who have already left (those with leaving time ≤ current arrival time), returning their chairs to the available pool.

The elegant part is that we only need n chairs maximum (even though infinite chairs exist) because at most n friends can be at the party simultaneously. So we initialize our available chairs heap with just 0 to n-1.

By processing friends in arrival order and maintaining these two heaps - one for available chairs (smallest first) and one for when occupied chairs will be freed (earliest leaving time first) - we can efficiently simulate the entire party and find which chair our target friend sits on.

Learn more about Heap (Priority Queue) patterns.

Solution Approach

The implementation follows these steps:

  1. Data Preparation: First, we augment each friend's data with their index. For each friend i, we transform times[i] = [arrival, leaving] into [arrival, leaving, i]. This allows us to track which friend is which after sorting.

  2. Sort by Arrival Time: We sort the augmented times array by arrival time. This ensures we process friends in the order they arrive at the party.

  3. Initialize Data Structures:

    • Create a min-heap idle containing all chair numbers from 0 to n-1 using list(range(n)) and heapify(idle)
    • Create an empty min-heap busy to store (leaving_time, chair_number) tuples
  4. Process Each Friend: Iterate through the sorted friends:

    a. Free Up Chairs: Before assigning a chair to the current friend, check if any previous friends have left:

    while busy and busy[0][0] <= arrival:
        heappush(idle, heappop(busy)[1])

    This removes all friends from busy whose leaving time is ≤ current arrival time, and returns their chairs to idle.

    b. Assign Chair: Pop the smallest available chair from idle:

    j = heappop(idle)

    c. Check Target: If this is our target friend, return the chair number:

    if i == targetFriend:
        return j

    d. Mark Chair as Busy: Otherwise, add this friend's leaving time and chair to busy:

    heappush(busy, (leaving, j))

The time complexity is O(n log n) due to sorting and heap operations. The space complexity is O(n) for the heaps. The algorithm efficiently simulates the chair assignment process while maintaining the invariant that friends always get the smallest available chair number.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example to see how the two-heap solution works.

Example Input:

  • times = [[1,4], [2,3], [4,6]] (3 friends)
  • targetFriend = 1 (we want to know where friend 1 sits)

Step 1: Data Preparation We augment each friend's data with their index:

  • Friend 0: [1, 4, 0] (arrives at 1, leaves at 4)
  • Friend 1: [2, 3, 1] (arrives at 2, leaves at 3)
  • Friend 2: [4, 6, 2] (arrives at 4, leaves at 6)

After sorting by arrival time, the order remains the same (already sorted).

Step 2: Initialize Heaps

  • idle = [0, 1, 2] (all chairs initially available)
  • busy = [] (no chairs occupied yet)

Step 3: Process Friends in Arrival Order

Time = 1 (Friend 0 arrives):

  • Check busy heap: empty, so no chairs to free
  • Pop smallest chair from idle: chair 0
  • Friend 0 is not our target
  • Push (4, 0) to busy (chair 0 will be free at time 4)
  • State: idle = [1, 2], busy = [(4, 0)]

Time = 2 (Friend 1 arrives):

  • Check busy heap: (4, 0) has leaving time 4 > 2, so no chairs freed
  • Pop smallest chair from idle: chair 1
  • Friend 1 IS our target! Return chair 1

Let's continue to see what would happen with Friend 2:

Time = 4 (Friend 2 arrives):

  • Check busy heap: (4, 0) has leaving time 4 ≤ 4, so free chair 0
    • Pop (4, 0) from busy and push chair 0 to idle
  • Check again: (3, 1) would have leaving time 3 ≤ 4, so free chair 1 too
    • Pop (3, 1) from busy and push chair 1 to idle
  • State before assignment: idle = [0, 1, 2], busy = []
  • Pop smallest chair from idle: chair 0
  • Friend 2 sits on chair 0

Answer: Friend 1 sits on chair 1

This example demonstrates how:

  • The idle heap always provides the smallest available chair
  • The busy heap tracks when chairs become available again
  • Chairs are reused once friends leave (Friend 2 gets chair 0 after Friend 0 leaves)
  • The algorithm correctly handles the rule that friends take the smallest available chair

Solution Implementation

1from typing import List
2import heapq
3
4class Solution:
5    def smallestChair(self, times: List[List[int]], targetFriend: int) -> int:
6        # Get the total number of friends
7        n = len(times)
8      
9        # Add friend index to each time interval for tracking
10        for i in range(n):
11            times[i].append(i)
12      
13        # Sort friends by arrival time
14        times.sort()
15      
16        # Min-heap of available chair numbers (initially all chairs 0 to n-1)
17        available_chairs = list(range(n))
18        heapq.heapify(available_chairs)
19      
20        # Min-heap of (leaving_time, chair_number) for occupied chairs
21        occupied_chairs = []
22      
23        # Process each friend in order of arrival
24        for arrival_time, leaving_time, friend_index in times:
25            # Free up chairs from friends who have already left
26            while occupied_chairs and occupied_chairs[0][0] <= arrival_time:
27                leaving_time_finished, chair_to_free = heapq.heappop(occupied_chairs)
28                heapq.heappush(available_chairs, chair_to_free)
29          
30            # Assign the smallest available chair to current friend
31            assigned_chair = heapq.heappop(available_chairs)
32          
33            # If this is the target friend, return their chair number
34            if friend_index == targetFriend:
35                return assigned_chair
36          
37            # Mark this chair as occupied until leaving_time
38            heapq.heappush(occupied_chairs, (leaving_time, assigned_chair))
39      
40        # Should never reach here given problem constraints
41        return -1
42
1class Solution {
2    public int smallestChair(int[][] times, int targetFriend) {
3        int n = times.length;
4      
5        // Min heap to store available chair numbers
6        PriorityQueue<Integer> availableChairs = new PriorityQueue<>();
7      
8        // Min heap to store occupied chairs sorted by leaving time
9        // Each element is [leavingTime, chairNumber]
10        PriorityQueue<int[]> occupiedChairs = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
11      
12        // Initialize available chairs (0 to n-1)
13        for (int i = 0; i < n; i++) {
14            availableChairs.offer(i);
15        }
16      
17        // Transform times array to include friend index: [arrival, leaving, friendIndex]
18        for (int i = 0; i < n; i++) {
19            times[i] = new int[] {times[i][0], times[i][1], i};
20        }
21      
22        // Sort friends by arrival time
23        Arrays.sort(times, Comparator.comparingInt(a -> a[0]));
24      
25        // Process each friend in order of arrival
26        for (int[] friend : times) {
27            int arrivalTime = friend[0];
28            int leavingTime = friend[1];
29            int friendIndex = friend[2];
30          
31            // Release chairs from friends who have already left
32            while (!occupiedChairs.isEmpty() && occupiedChairs.peek()[0] <= arrivalTime) {
33                int freedChair = occupiedChairs.poll()[1];
34                availableChairs.offer(freedChair);
35            }
36          
37            // Assign the smallest available chair to current friend
38            int assignedChair = availableChairs.poll();
39          
40            // Check if this is the target friend
41            if (friendIndex == targetFriend) {
42                return assignedChair;
43            }
44          
45            // Mark this chair as occupied until leaving time
46            occupiedChairs.offer(new int[] {leavingTime, assignedChair});
47        }
48      
49        // This should never be reached given valid input
50        return -1;
51    }
52}
53
1class Solution {
2public:
3    int smallestChair(vector<vector<int>>& times, int targetFriend) {
4        // Define pair type for convenience (time, chair_number)
5        using pii = pair<int, int>;
6      
7        // Min heap to track busy chairs: (leaving_time, chair_number)
8        priority_queue<pii, vector<pii>, greater<pii>> busyChairs;
9      
10        // Min heap to track available chairs (smallest chair number first)
11        priority_queue<int, vector<int>, greater<int>> availableChairs;
12      
13        int n = times.size();
14      
15        // Add friend index to each time entry and initialize available chairs
16        for (int i = 0; i < n; ++i) {
17            times[i].push_back(i);  // Append friend index to [arrival, leaving]
18            availableChairs.push(i); // Initially all chairs 0 to n-1 are available
19        }
20      
21        // Sort friends by arrival time
22        ranges::sort(times);
23      
24        // Process each friend in order of arrival
25        for (const auto& friendInfo : times) {
26            int arrivalTime = friendInfo[0];
27            int leavingTime = friendInfo[1];
28            int friendIndex = friendInfo[2];
29          
30            // Free up chairs from friends who have left before current arrival
31            while (!busyChairs.empty() && busyChairs.top().first <= arrivalTime) {
32                int freedChair = busyChairs.top().second;
33                availableChairs.push(freedChair);
34                busyChairs.pop();
35            }
36          
37            // Assign the smallest available chair to current friend
38            int assignedChair = availableChairs.top();
39          
40            // If this is the target friend, return their chair number
41            if (friendIndex == targetFriend) {
42                return assignedChair;
43            }
44          
45            // Mark chair as busy and record when it will be free
46            availableChairs.pop();
47            busyChairs.emplace(leavingTime, assignedChair);
48        }
49      
50        // Should not reach here if input is valid
51        return -1;
52    }
53};
54
1/**
2 * Finds the smallest chair number that the target friend will sit on
3 * @param times - Array of [arrival, leaving] times for each friend
4 * @param targetFriend - Index of the friend whose chair we want to find
5 * @returns The chair number (0-indexed) that the target friend sits on
6 */
7function smallestChair(times: number[][], targetFriend: number): number {
8    const friendCount = times.length;
9  
10    // Priority queue for available chairs (smallest chair number has priority)
11    const availableChairs = new MinPriorityQueue();
12  
13    // Priority queue for occupied chairs, sorted by leaving time
14    // Each element is [leavingTime, chairNumber]
15    const occupiedChairs = new MinPriorityQueue({ priority: (item) => item[0] });
16  
17    // Add friend index to each time entry and initialize all chairs as available
18    for (let friendIndex = 0; friendIndex < friendCount; ++friendIndex) {
19        times[friendIndex].push(friendIndex);
20        availableChairs.enqueue(friendIndex);
21    }
22  
23    // Sort friends by arrival time
24    times.sort((a, b) => a[0] - b[0]);
25  
26    // Process each friend in order of arrival
27    for (const [arrivalTime, leavingTime, friendIndex] of times) {
28        // Free up chairs from friends who have already left
29        while (occupiedChairs.size() > 0 && occupiedChairs.front().element[0] <= arrivalTime) {
30            const freedChair = occupiedChairs.dequeue().element[1];
31            availableChairs.enqueue(freedChair);
32        }
33      
34        // Assign the smallest available chair to current friend
35        const assignedChair = availableChairs.dequeue().element;
36      
37        // If this is our target friend, return their chair number
38        if (friendIndex === targetFriend) {
39            return assignedChair;
40        }
41      
42        // Mark the chair as occupied until leaving time
43        occupiedChairs.enqueue([leavingTime, assignedChair]);
44    }
45  
46    // Should never reach here if input is valid
47    return -1;
48}
49

Time and Space Complexity

Time Complexity: O(n × log n)

The time complexity is dominated by several operations:

  • Adding indices to each time entry: O(n)
  • Sorting the times array: O(n × log n)
  • Heapifying the idle list: O(n)
  • Main loop iterating through n friends: O(n)
    • Within each iteration:
      • While loop with heap operations: Each friend is pushed and popped from the busy heap at most once, so amortized O(log n) per iteration
      • Heap pop from idle: O(log n)
      • Heap push to busy: O(log n)

The overall complexity is O(n) + O(n × log n) + O(n) + O(n × log n) = O(n × log n)

Space Complexity: O(n)

The space complexity consists of:

  • Modified times array with appended indices: O(n)
  • idle heap containing up to n chair indices: O(n)
  • busy heap containing at most n entries: O(n)

Total space used is O(n) + O(n) + O(n) = O(n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Forgetting to Track Original Friend Indices

One of the most common mistakes is sorting the times array by arrival time without preserving the original friend indices. After sorting, you lose track of which friend is which, making it impossible to identify when the targetFriend arrives.

Incorrect approach:

times.sort()  # Lost original indices!
for arrival, leaving in times:
    # Cannot determine if this is targetFriend

Correct approach:

# Add index before sorting
for i in range(n):
    times[i].append(i)
times.sort()
# Now we can check: if friend_index == targetFriend

2. Not Freeing Chairs at the Exact Arrival Time

A critical detail is that if a friend leaves at time t and another arrives at time t, the arriving friend CAN take the just-vacated chair. The condition for freeing chairs must use <= not <.

Incorrect:

while occupied_chairs and occupied_chairs[0][0] < arrival_time:  # Wrong!
    # This misses chairs that become available at exact arrival time

Correct:

while occupied_chairs and occupied_chairs[0][0] <= arrival_time:
    # Correctly frees chairs available at or before arrival

3. Using Too Many Chairs

Since we know there are exactly n friends and they can't all be present simultaneously (arrival times are distinct), we only need n chairs maximum. Creating an unnecessarily large chair pool wastes memory and time.

Inefficient:

available_chairs = list(range(10000))  # Wasteful!

Efficient:

available_chairs = list(range(n))  # Exactly what we need

4. Incorrect Heap Structure for Occupied Chairs

The occupied chairs heap must be ordered by leaving time (when the chair becomes free), not by chair number. Storing just chair numbers or using the wrong ordering breaks the logic.

Incorrect:

heapq.heappush(occupied_chairs, assigned_chair)  # No leaving time!
# or
heapq.heappush(occupied_chairs, (assigned_chair, leaving_time))  # Wrong order!

Correct:

heapq.heappush(occupied_chairs, (leaving_time, assigned_chair))
# Ensures we process by leaving time first
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement recursion?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More