The Number of the Smallest Unoccupied Chair
MedPrompt
There is a party where n friends numbered from 0 to n - 1 are attending. There is an infinite number of chairs in this party that are numbered from 0 to infinity. When a friend arrives at the party, they sit on the unoccupied chair with the smallest number.
- For example, if chairs
0,1, and5are occupied when a friend comes, they will sit on chair number2.
When a friend leaves the party, their chair becomes unoccupied at the moment they leave. If another friend arrives at that same moment, they can sit in that chair.
You are given a 0-indexed 2D integer array times where times[i] = [arrivali, leavingi], indicating the arrival and leaving times of the ith friend respectively, and an integer targetFriend. All arrival times are distinct.
Return the chair number that the friend numbered targetFriend will sit on.
Example 1:
Input: times = [[1,4],[2,3],[4,6]], targetFriend = 1
Output: 1
Explanation:
- Friend 0 arrives at time 1 and sits on chair 0.
- Friend 1 arrives at time 2 and sits on chair 1.
- Friend 1 leaves at time 3 and chair 1 becomes empty.
- Friend 0 leaves at time 4 and chair 0 becomes empty.
- Friend 2 arrives at time 4 and sits on chair 0.
Since friend 1 sat on chair 1, we return 1.Example 2:
Input: times = [[3,10],[1,5],[2,6]], targetFriend = 0
Output: 2
Explanation:
- Friend 1 arrives at time 1 and sits on chair 0.
- Friend 2 arrives at time 2 and sits on chair 1.
- Friend 0 arrives at time 3 and sits on chair 2.
- Friend 1 leaves at time 5 and chair 0 becomes empty.
- Friend 2 leaves at time 6 and chair 1 becomes empty.
- Friend 0 leaves at time 10 and chair 2 becomes empty.
Since friend 0 sat on chair 2, we return 2.
Constraints:
n == times.length2 <= n <= 104times[i].length == 21 <= arrivali < leavingi <= 1050 <= targetFriend <= n - 1- Each
arrivalitime is distinct.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach simulates the party second-by-second. It iterates through time from the earliest arrival to the latest departure, processing any arrivals or departures that occur at each time step.
Algorithm
- Initialize a map
arrivalsto store friend information (leaving_time,friend_index) keyed by theirarrival_time. - Initialize another map
departuresto store lists of chairs that become free, keyed byleaving_time. - Initialize a min-priority queue
availableChairsto manage unoccupied chairs and a counternextChairfor new chairs. - Find the maximum event time
maxTimefrom the input. - Loop through time
tfrom 1 tomaxTime: a. Checkdeparturesfor timet. Add any freed chairs toavailableChairs. b. Checkarrivalsfor timet. If a friend arrives: i. Assign a chair: poll fromavailableChairsor usenextChair++. ii. If the friend istargetFriend, return the assigned chair. iii. Schedule the departure by adding the chair and leaving time to thedeparturesmap.
Walkthrough
This method simulates the process chronologically by iterating through time from 1 up to the maximum possible event time. We use data structures to keep track of arrivals and departures scheduled for each time unit.
We use a hash map arrivals where arrivals.get(t) gives the friend arriving at time t and their leaving time. Another hash map departures maps a leaving time t to a list of chairs that become free at that time. A min-priority queue, availableChairs, keeps track of all unoccupied chairs, ensuring we can always find the one with the smallest number. A counter next_chair_num tracks the next new chair to be used if no freed chairs are available.
The simulation proceeds in a loop from t = 1 to the maximum time. At each time t, we first process departures by checking the departures map and adding any freed chairs to availableChairs. Then, we process arrivals. If a friend arrives, we assign them a chair from availableChairs or a new one. We record the assignment and schedule their departure. If the arriving friend is the targetFriend, we have found our answer and can return it.
import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.PriorityQueue; class Solution { public int smallestChair(int[][] times, int targetFriend) { int maxTime = 0; for (int[] time : times) { maxTime = Math.max(maxTime, time[1]); } Map<Integer, int[]> arrivals = new HashMap<>(); for (int i = 0; i < times.length; i++) { arrivals.put(times[i][0], new int[]{times[i][1], i}); } Map<Integer, List<Integer>> departures = new HashMap<>(); PriorityQueue<Integer> availableChairs = new PriorityQueue<>(); int nextChair = 0; for (int t = 1; t <= maxTime; t++) { if (departures.containsKey(t)) { for (int chair : departures.get(t)) { availableChairs.offer(chair); } departures.remove(t); } if (arrivals.containsKey(t)) { int[] arrivalInfo = arrivals.get(t); int leavingTime = arrivalInfo[0]; int friendIndex = arrivalInfo[1]; int chairToAssign; if (!availableChairs.isEmpty()) { chairToAssign = availableChairs.poll(); } else { chairToAssign = nextChair++; } if (friendIndex == targetFriend) { return chairToAssign; } departures.computeIfAbsent(leavingTime, k -> new ArrayList<>()).add(chairToAssign); } } return -1; // Should not be reached }}Complexity
Time
O(T_max + N log N). The main loop runs T_max times. The total work for priority queue operations over the entire simulation is O(N log N).
Space
O(N + T_max), where N is the number of friends and T_max is the maximum leaving time. The maps for arrivals and departures can store up to T_max entries.
Trade-offs
Pros
Conceptually simple as it directly models the flow of time.
Cons
Inefficient time complexity if the time range is much larger than the number of friends.
High space complexity due to maps that can scale with the maximum time.
This is a more efficient approach that processes events (friend arrivals) in chronological order, jumping from one event time to the next. It avoids iterating through empty time intervals. By sorting friends based on their arrival times and using priority queues, we can efficiently manage chair allocation.
Algorithm
- Create an augmented data structure to store
[arrival, leaving, original_index]for each friend. - Sort this structure based on arrival times to process events chronologically.
- Initialize two min-priority queues:
available_chairsfor free chair numbers, andoccupied_chairsfor[leaving_time, chair_number]pairs, sorted by leaving time. - Initialize a
nextChaircounter to 0. - Iterate through the sorted friends:
a. For the current friend's
arrival_time, free up chairs by moving them fromoccupied_chairstoavailable_chairsif theirleaving_timeis less than or equal to thearrival_time. b. Assign a chair: poll fromavailable_chairs. If empty, usenextChairand increment it. c. If the current friend is thetargetFriend, return the assigned chair number. d. Add the[leaving_time, assigned_chair]pair tooccupied_chairs.
Walkthrough
This approach improves upon brute-force by only considering time points where an event (a friend arriving) occurs. This avoids wasteful iteration over empty time intervals.
First, we augment the times array to include the original index of each friend, creating entries of the form [arrival, leaving, original_index]. This is crucial for identifying the targetFriend after sorting. We then sort this augmented array based on arrival times.
We use two min-priority queues to manage the state of the chairs:
available_chairs: Stores integers representing chair numbers that are free. Polling from this gives the smallest-numbered available chair.occupied_chairs: Stores pairs of[leaving_time, chair_number], ordered byleaving_time. This allows us to efficiently find which chairs become free next.
We iterate through the sorted friends. For each arriving friend, we first process all departures that have occurred up to the current friend's arrival time. We do this by checking the top of occupied_chairs and moving any freed chairs to available_chairs. Then, we assign the smallest available chair to the new arrival. If the arriving friend is our target, we return their assigned chair number. Otherwise, we add their chair and leaving time to the occupied_chairs queue.
import java.util.Arrays;import java.util.PriorityQueue; class Solution { public int smallestChair(int[][] times, int targetFriend) { int n = times.length; int[][] friends = new int[n][3]; for (int i = 0; i < n; i++) { friends[i][0] = times[i][0]; // arrival friends[i][1] = times[i][1]; // leaving friends[i][2] = i; // original index } Arrays.sort(friends, (a, b) -> Integer.compare(a[0], b[0])); PriorityQueue<Integer> availableChairs = new PriorityQueue<>(); PriorityQueue<int[]> occupiedChairs = new PriorityQueue<>((a, b) -> Integer.compare(a[0], b[0])); int nextChair = 0; for (int[] friend : friends) { int arrival = friend[0]; int leaving = friend[1]; int index = friend[2]; while (!occupiedChairs.isEmpty() && occupiedChairs.peek()[0] <= arrival) { int[] freed = occupiedChairs.poll(); availableChairs.offer(freed[1]); } int chairToAssign; if (!availableChairs.isEmpty()) { chairToAssign = availableChairs.poll(); } else { chairToAssign = nextChair++; } if (index == targetFriend) { return chairToAssign; } occupiedChairs.offer(new int[]{leaving, chairToAssign}); } return -1; // Should not be reached }}Complexity
Time
O(N log N). Sorting the N friends takes O(N log N). The loop processes each friend once, and the total time for all priority queue operations is also O(N log N).
Space
O(N), where N is the number of friends. We need O(N) space for the augmented friend data and for the priority queues.
Trade-offs
Pros
Optimal time complexity, independent of the time range.
Efficient space usage.
Cons
Slightly more complex to implement due to managing friend indices and multiple data structures.
Solutions
Solution
/** * @param {number[][]} times * @param {number} targetFriend * @return {number} */ var smallestChair = function (times, targetFriend) { const n = times.length; const idle = new MinPriorityQueue(); const busy = new MinPriorityQueue({ priority: (v) => v[0] }); for (let i = 0; i < n; ++i) { times[i].push(i); idle.enqueue(i); } times.sort((a, b) => a[0] - b[0]); for (const [arrival, leaving, i] of times) { while (busy.size() > 0 && busy.front().element[0] <= arrival) { idle.enqueue(busy.dequeue().element[1]); } const j = idle.dequeue().element; if (i === targetFriend) { return j; } busy.enqueue([leaving, j]); } };class Solution {public int smallestChair(int[][] times, int targetFriend) { int n = times.length; int[][] ts = new int[n][3]; PriorityQueue<Integer> q = new PriorityQueue<>(); PriorityQueue<int[]> busy = new PriorityQueue<>((a, b)->a[0] - b[0]); for (int i = 0; i < n; ++i) { ts[i] = new int[]{times[i][0], times[i][1], i}; q.offer(i); } Arrays.sort(ts, (a, b)->a[0] - b[0]); for (int[] t : ts) { int a = t[0], b = t[1], i = t[2]; while (!busy.isEmpty() && busy.peek()[0] <= a) { q.offer(busy.poll()[1]); } int c = q.poll(); if (i == targetFriend) { return c; } busy.offer(new int[]{b, c}); } return -1; }}using pii = pair < int , int > ; class Solution { public: int smallestChair ( vector < vector < int >>& times , int targetFriend ) { priority_queue < int , vector < int > , greater < int >> q ; priority_queue < pii , vector < pii > , greater < pii >> busy ; int n = times . size (); for ( int i = 0 ; i < n ; ++ i ) { times [ i ]. push_back ( i ); q . push ( i ); } sort ( times . begin (), times . end ()); for ( auto & t : times ) { int a = t [ 0 ], b = t [ 1 ], i = t [ 2 ]; while ( ! busy . empty () && busy . top (). first <= a ) { q . push ( busy . top (). second ); busy . pop (); } int c = q . top (); q . pop (); if ( i == targetFriend ) return c ; busy . push ({ b , c }); } return - 1 ; } };class Solution: def smallestChair(self, times: List[List[int]], targetFriend: int) -> int: n = len(times) h = list(range(n)) heapify(h) for i in range(n): times[i]. append(i) times . sort() busy = [] for a, b, i in times: while busy and busy[0][0] <= a: heappush(h, heappop(busy)[1]) c = heappop(h) if i == targetFriend: return c heappush(busy, (b, c)) return - 1Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.