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
wheretimes[i] = [arrival_i, leaving_i]
represents when thei
th 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.
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:
- Some friends might have left, freeing up their chairs
- 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:
-
Data Preparation: First, we augment each friend's data with their index. For each friend
i
, we transformtimes[i] = [arrival, leaving]
into[arrival, leaving, i]
. This allows us to track which friend is which after sorting. -
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. -
Initialize Data Structures:
- Create a min-heap
idle
containing all chair numbers from0
ton-1
usinglist(range(n))
andheapify(idle)
- Create an empty min-heap
busy
to store(leaving_time, chair_number)
tuples
- Create a min-heap
-
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 toidle
.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 EvaluatorExample 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)
tobusy
(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)
frombusy
and push chair 0 toidle
- Pop
- Check again:
(3, 1)
would have leaving time 3 ≤ 4, so free chair 1 too- Pop
(3, 1)
frombusy
and push chair 1 toidle
- Pop
- 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)
- While loop with heap operations: Each friend is pushed and popped from the busy heap at most once, so amortized
- Within each iteration:
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
Which data structure is used to implement recursion?
Recommended Readings
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!