Taro Logo

Seat Reservation Manager

Medium
Dropbox logo
Dropbox
2 views
Topics:
ArraysGreedy Algorithms

Design a system that manages the reservation state of n seats that are numbered from 1 to n.

Implement the SeatManager class:

  • SeatManager(int n) Initializes a SeatManager object that will manage n seats numbered from 1 to n. All seats are initially available.
  • int reserve() Fetches the smallest-numbered unreserved seat, reserves it, and returns its number.
  • void unreserve(int seatNumber) Unreserves the seat with the given seatNumber.

Example 1:

Input
["SeatManager", "reserve", "reserve", "unreserve", "reserve", "reserve", "reserve", "reserve", "unreserve"]
[[5], [], [], [2], [], [], [], [], [5]]
Output
[null, 1, 2, null, 2, 3, 4, 5, null]

Explanation
SeatManager seatManager = new SeatManager(5); // Initializes a SeatManager with 5 seats.
seatManager.reserve();    // All seats are available, so return the lowest numbered seat, which is 1.
seatManager.reserve();    // The available seats are [2,3,4,5], so return the lowest of them, which is 2.
seatManager.unreserve(2); // Unreserve seat 2, so now the available seats are [2,3,4,5].
seatManager.reserve();    // The available seats are [2,3,4,5], so return the lowest of them, which is 2.
seatManager.reserve();    // The available seats are [3,4,5], so return the lowest of them, which is 3.
seatManager.reserve();    // The available seats are [4,5], so return the lowest of them, which is 4.
seatManager.reserve();    // The only available seat is seat 5, so return 5.
seatManager.unreserve(5); // Unreserve seat 5, so now the available seats are [5].

Constraints:

  • 1 <= n <= 105
  • 1 <= seatNumber <= n
  • For each call to reserve, it is guaranteed that there will be at least one unreserved seat.
  • For each call to unreserve, it is guaranteed that seatNumber will be reserved.
  • At most 105 calls in total will be made to reserve and unreserve.

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What is the maximum value of `n` (the number of seats), and what are the performance expectations for reserve and unreserve calls?
  2. Is `seatNumber` in the `unreserve` method guaranteed to be a previously reserved seat, or should I handle cases where an invalid seat number is unreserved?
  3. Can `n` be zero or negative? If so, how should I handle those cases?
  4. Are the calls to `reserve` and `unreserve` interleaved, or could there be a long sequence of only `reserve` calls followed by a long sequence of `unreserve` calls?
  5. If multiple seats are available, is the requirement *strictly* to reserve the smallest-numbered seat, or can I reserve any available seat if it simplifies the implementation and maintains the smallest-numbered seat will eventually be reserved?

Brute Force Solution

Approach

The brute force approach to managing seat reservations involves going through every single seat to find available ones. It's like checking each seat one by one when someone asks for a reservation, or to see if a seat is open. This continues until a suitable seat is found, or all seats have been checked.

Here's how the algorithm would work step-by-step:

  1. When someone wants to reserve a seat, start by checking seat number one.
  2. If seat number one is available, reserve it for them and we are done.
  3. If seat number one is taken, check seat number two.
  4. Keep going, checking each seat in order (seat number three, seat number four, and so on).
  5. If you find an available seat, reserve it and stop looking.
  6. If you check every single seat and none are available, then there are no seats to reserve.

Code Implementation

class SeatReservationManager:

    def __init__(self, number_of_seats):
        self.seats = [False] * number_of_seats

    def reserve(self):
        # Iterate through each seat to find an available one
        for seat_index in range(len(self.seats)):

            #Check if current seat is available
            if not self.seats[seat_index]:
                self.seats[seat_index] = True

                # Mark the seat as reserved and return
                return seat_index

        #No seat available to reserve
        return None

    def unreserve(self, seat_number):
        #Validate seat number
        if 0 <= seat_number < len(self.seats):

            #Ensure that the seat has been reserved
            if self.seats[seat_number]:
                self.seats[seat_number] = False

Big(O) Analysis

Time Complexity
O(n)The time complexity is determined by the process of finding an available seat. In the worst-case scenario, the algorithm iterates through all 'n' seats to find one that is available. If all the lower-numbered seats are taken, the algorithm will have to check each seat sequentially until it reaches the last seat or finds an open one. Therefore, the maximum number of operations directly scales with the number of seats 'n', resulting in a linear time complexity of O(n).
Space Complexity
O(1)The provided brute force approach iterates through seats without using any auxiliary data structures. It only requires a constant amount of extra space to store a few variables, such as the current seat being checked. The amount of extra memory used doesn't depend on the number of seats, which we can denote as N. Therefore, the auxiliary space complexity is O(1).

Optimal Solution

Approach

We need a system to keep track of available seats in a theater and efficiently assign them to customers. The best way to do this is by using a special structure that always knows what the smallest unreserved seat number is, allowing us to assign seats quickly and keep track of what's free.

Here's how the algorithm would work step-by-step:

  1. First, create a system that holds all the seat numbers and can quickly give us the smallest unreserved seat.
  2. When someone asks to reserve a seat, simply take the smallest available seat number from our system and mark it as reserved.
  3. When someone frees up a seat, add that seat number back into our system so it can be reserved again.
  4. The magic behind this system is that it always knows the smallest available seat, so we don't have to search for it, making reservations and releasing seats very fast.

Code Implementation

import heapq

class SeatReservationManager:

    def __init__(self, number_of_seats): 
        self.available_seats = list(range(1, number_of_seats + 1))
        heapq.heapify(self.available_seats)

    def reserve(self): 
        # Always reserve the smallest available seat.
        if self.available_seats:
            return heapq.heappop(self.available_seats)
        else:
            return None

    def unreserve(self, seat_number): 
        # Add the unreserved seat back to available seats
        heapq.heappush(self.available_seats, seat_number)

Big(O) Analysis

Time Complexity
O(log n)The Seat Reservation Manager uses a data structure, most likely a min-heap or a priority queue, to efficiently store and retrieve the smallest unreserved seat number. Reserving a seat involves removing the smallest element from the heap (or priority queue), which takes O(log n) time, where n is the number of unreserved seats. Releasing a seat involves adding the seat number back to the heap (or priority queue), also taking O(log n) time. Therefore, both reserve and unreserve operations have a time complexity of O(log n).
Space Complexity
O(N)The system needs to hold all seat numbers. In the worst case, all N seats are unreserved initially, requiring storage for N seat numbers, likely within a data structure like a min-heap or a set. When a seat is freed up, it's added back into this data structure. Therefore, the auxiliary space used grows linearly with the total number of seats, N, to store the available seat numbers.

Edge Cases

CaseHow to Handle
n is zero or negative in the constructorThrow an IllegalArgumentException or similar to indicate invalid input; zero seats don't make sense.
reserve() called when all seats are already reservedThe priority queue or sorted set will be empty; return an error value (e.g., -1) or throw an exception.
unreserve() called with a seat number that is already unreserved or was never reservedThe priority queue or sorted set already contains the number; ensure no double-insertion or other undefined behavior occurs; can also be ignored if desired.
n is a very large number, close to the maximum integer valueEnsure that the data structures used (priority queue or sorted set) can handle a large number of elements without memory issues or integer overflow.
A sequence of reserve() and unreserve() operations leads to extreme fragmentation, meaning many small gaps of unreserved seats existThe priority queue or sorted set handles this by maintaining the smallest available seat, though performance could degrade if very fragmented.
Mixed calls to reserve() and unreserve() are made where reserve() retrieves an element just unreserved.The data structure implementation must ensure consistency between reserve and unreserve so a recently unreserved seat can immediately be grabbed.
Memory exhaustion during a large number of reserve and unreserve operationsMonitor memory usage to prevent crashes, and consider alternative data structures if memory becomes a bottleneck.
Seat numbers close to Integer.MAX_VALUE or Integer.MIN_VALUE when unreservingAvoid integer overflow issues when manipulating or storing seat numbers by using appropriate data types (long if needed) or carefully managing the range.