Problem Overview
Seat Reservation Manager (LeetCode 1845) asks you to design a data structure that manages n seats numbered 1 to n. You must support two operations: reserve(), which returns the smallest available seat number and marks it as reserved, and unreserve(seatNumber), which marks a previously reserved seat as available again.
The challenge is efficiency. A naive approach using a sorted array and linear scan gives O(n) per reserve and O(n) per unreserve due to insertion sorting. With up to 10^5 calls, a linear approach will time out. The goal is O(log n) per operation.
This problem belongs to the data structure design family on LeetCode — problems where you implement a class with specific methods. The key insight is recognizing which data structure naturally maintains a sorted order with fast extraction of the minimum element.
- n seats numbered 1 to n — all initially available
- reserve() returns the smallest available seat number
- unreserve(seatNumber) makes that seat available again
- Up to 10^5 calls to reserve and unreserve
- Return a single integer from reserve()
Min-Heap for Smallest Available
A min-heap is the ideal data structure for this problem. It maintains a collection of elements such that the minimum is always accessible at the top. Extraction of the minimum (heappop) runs in O(log n) time, and insertion (heappush) also runs in O(log n) time.
The mapping to this problem is direct: store all available seat numbers in the min-heap. To reserve a seat, call heappop — this removes and returns the smallest available seat in O(log n). To unreserve a seat, call heappush with the seat number — this inserts it back into the heap in O(log n), restoring its availability.
A SortedSet (available in Java via TreeSet) also works: pollFirst() extracts the minimum and add() inserts back, both O(log n). However, a min-heap is simpler to implement in Python with the built-in heapq module and carries lower constant factors. Both approaches satisfy the time complexity requirement.
Min-heap is the perfect fit: O(log n) extract-min and O(log n) insert
A min-heap always has the smallest element at the top. reserve() = heappop (O log n); unreserve() = heappush (O log n). A SortedSet also works but min-heap is simpler and has lower overhead for this problem.
Constructor Initialization
The eager approach initializes the min-heap in the constructor by pushing all seat numbers 1 through n onto the heap. In Python: self.heap = list(range(1, n + 1)) followed by heapq.heapify(self.heap). The heapify call converts the list to a valid heap in O(n) linear time, which is better than pushing one by one (O(n log n)).
After construction, the heap contains all n seats in min-heap order. Every subsequent reserve or unreserve call runs in O(log n). The total memory used is O(n) to store all seat numbers, which is unavoidable since all seats must be tracked.
An alternative is the lazy initialization approach, which avoids the O(n) constructor cost. Instead of pushing all seats, maintain a counter starting at 1 that tracks the next natural seat to hand out. This trades constructor time for slightly more logic per operation.
- 1Constructor: push all seats 1..n onto a min-heap
- 2Use heapq.heapify for O(n) setup instead of n separate heappush calls
- 3reserve(): call heapq.heappop to get the smallest available seat in O(log n)
- 4unreserve(seatNumber): call heapq.heappush to return the seat in O(log n)
- 5Return the popped seat number from reserve()
Lazy Initialization Alternative
The lazy approach avoids pushing all n seats at construction time. Instead, maintain a counter starting at 1 and an empty min-heap for returned seats. In reserve(): if the heap is empty, return counter and increment it (next natural seat); otherwise, heappop from the heap (a previously returned seat that may be smaller than counter).
In unreserve(seatNumber): always push seatNumber onto the heap. The heap stores only seats that have been returned via unreserve — not all available seats. The counter handles the tail of available seats that have never been returned.
This approach uses O(1) constructor time and O(k) memory where k is the number of currently unreserved-but-not-yet-reserved-again seats. For workloads with few unreserve calls or large n, this is significantly more memory-efficient than the eager approach.
Lazy approach: O(1) constructor, heap stores only returned seats
Instead of pushing all n seats upfront, maintain a counter for next natural seat and a heap for returned seats. reserve() checks the heap first (returned seats may be smaller than counter); unreserve() always pushes to the heap. Constructor is O(1) and memory is proportional to operations, not total seats.
Walk-Through Example
Consider SeatManager(5): seats 1-5 are available. Using the eager approach, the heap starts as [1, 2, 3, 4, 5]. The first reserve() call pops 1 → heap = [2, 3, 4, 5], returns 1. The second reserve() pops 2 → heap = [3, 4, 5], returns 2.
Now unreserve(2): push 2 back → heap = [2, 3, 4, 5]. The next reserve() pops 2 again (it is the smallest) → heap = [3, 4, 5], returns 2. Then reserve() → pops 3, returns 3. After all operations: heap = [4, 5].
Using the lazy approach: counter starts at 1, heap empty. reserve() → counter=1, return 1, counter becomes 2. reserve() → heap empty, return 2, counter becomes 3. unreserve(2) → push 2, heap=[2]. reserve() → heap non-empty, pop 2, return 2. reserve() → heap empty, return 3, counter becomes 4. State: counter=4, heap=[].
- 1SeatManager(5): heap = [1, 2, 3, 4, 5] (eager) or counter=1, heap=[] (lazy)
- 2reserve() → returns 1; heap = [2, 3, 4, 5]
- 3reserve() → returns 2; heap = [3, 4, 5]
- 4unreserve(2) → push 2; heap = [2, 3, 4, 5]
- 5reserve() → returns 2 (smallest); heap = [3, 4, 5]
- 6reserve() → returns 3; heap = [4, 5]
- 7Final heap state: [4, 5] — seats 4 and 5 still available
Code Walkthrough — Python and Java
Python (eager, heapq): in __init__, self.heap = list(range(1, n+1)); heapq.heapify(self.heap). reserve(): return heapq.heappop(self.heap). unreserve(seatNumber): heapq.heappush(self.heap, seatNumber). Python (lazy): __init__ sets self.heap = [] and self.counter = 1. reserve(): return heapq.heappop(self.heap) if self.heap else (self.counter := self.counter + 1) - 1. unreserve(): heapq.heappush(self.heap, seatNumber). Both run in O(log n) per call.
Java (eager, PriorityQueue): PriorityQueue<Integer> pq = new PriorityQueue<>(). In constructor, for (int i = 1; i <= n; i++) pq.offer(i). reserve(): return pq.poll(). unreserve(int seatNumber): pq.offer(seatNumber). Java (lazy): int counter = 1; PriorityQueue<Integer> pq = new PriorityQueue<>(). reserve(): return pq.isEmpty() ? counter++ : pq.poll(). unreserve(): pq.offer(seatNumber).
Time complexity for both languages and both approaches: O(log n) per reserve and unreserve call. Space: O(n) for eager, O(k) for lazy where k is the heap size at any point. The Java PriorityQueue is a min-heap by default, matching Python's heapq behavior.
For lazy approach: always check heap before counter
In the lazy approach, a returned seat (via unreserve) may have a seat number smaller than counter. Always check if the heap is non-empty first and pop from it — do not blindly use counter. Failing to check the heap first would return the wrong (larger) seat number when smaller returned seats are available.