import bisect
class ExamRoom:
def __init__(self, N):
self.N = N
self.seats = []
def seat(self):
if not self.seats:
self.seats.append(0)
return 0
max_dist = self.seats[0]
seat_idx = 0
for i in range(1, len(self.seats)):
prev = self.seats[i-1]
curr = self.seats[i]
dist = (curr - prev) // 2
if dist > max_dist:
max_dist = dist
seat_idx = prev + dist
if self.N - 1 - self.seats[-1] > max_dist:
seat_idx = self.N - 1
bisect.insort(self.seats, seat_idx)
return seat_idx
def leave(self, p):
idx = bisect.bisect_left(self.seats, p)
if idx < len(self.seats) and self.seats[idx] == p:
self.seats.pop(idx)
#include <set>
class ExamRoom {
int N;
std::set<int> seats;
public:
ExamRoom(int N): N(N) {}
int seat() {
if (seats.empty()) {
seats.insert(0);
return 0;
}
int dist = *seats.begin();
int seat_idx = 0;
int prev = -1;
for (int s : seats) {
if (prev != -1) {
int d = (s - prev) / 2;
if (d > dist) {
dist = d;
seat_idx = prev + d;
}
}
prev = s;
}
if (N - 1 - *seats.rbegin() > dist) {
seat_idx = N - 1;
}
seats.insert(seat_idx);
return seat_idx;
}
void leave(int p) {
seats.erase(p);
}
};
import java.util.TreeSet;
class ExamRoom {
private int N;
private TreeSet<Integer> seats;
public ExamRoom(int N) {
this.N = N;
this.seats = new TreeSet<>();
}
public int seat() {
if (seats.size() == 0) {
seats.add(0);
return 0;
}
int prev = -1, seat_idx = 0, dist = seats.first();
for (int s : seats) {
if (prev != -1) {
int d = (s - prev) / 2;
if (d > dist) {
dist = d;
seat_idx = prev + d;
}
}
prev = s;
}
if (N - 1 - seats.last() > dist) {
seat_idx = N - 1;
}
seats.add(seat_idx);
return seat_idx;
}
public void leave(int p) {
seats.remove(p);
}
}
class ExamRoom {
constructor(N) {
this.N = N;
this.seats = [];
}
seat() {
if (this.seats.length === 0) {
this.seats.push(0);
return 0;
}
this.seats.sort((a, b) => a - b);
let maxDist = this.seats[0];
let seatIdx = 0;
for (let i = 1; i < this.seats.length; i++) {
let prev = this.seats[i-1];
let curr = this.seats[i];
let d = Math.floor((curr - prev) / 2);
if (d > maxDist) {
maxDist = d;
seatIdx = prev + d;
}
}
if (this.N - 1 - this.seats[this.seats.length - 1] > maxDist) {
seatIdx = this.N - 1;
}
let idx = this.seats.findIndex(x => x > seatIdx);
if (idx === -1) this.seats.push(seatIdx);
else this.seats.splice(idx, 0, seatIdx);
return seatIdx;
}
leave(p) {
let idx = this.seats.indexOf(p);
if (idx !== -1) {
this.seats.splice(idx, 1);
}
}
}
You are designing an Exam Room with N
seats in a single row, numbered from 0
to N-1
. When a student enters, they must choose a seat such that the distance to the closest occupied seat is maximized. If there are multiple seats with the same maximum distance, the student chooses the seat with the lowest number. You must support two operations:
seat()
: A student enters and takes a seat following the rule above. Returns the seat number.leave(p)
: The student in seat p
leaves. It is guaranteed that seat p
is currently occupied.You need to implement a class that efficiently supports these operations for multiple calls, always maintaining the correct seat assignment and vacancy status.
At first glance, the problem seems to require checking all seats for every seat()
operation, which would be inefficient. The key insight is to focus on the gaps between occupied seats, as new students will always want to sit as far as possible from others. For each operation, we want to quickly find the largest available gap and determine the optimal seat within that gap.
If we store the occupied seats in a sorted list or set, we can efficiently find the largest gap between any two occupied seats, as well as the edges (beginning and end of the row). This allows us to avoid checking every seat and instead only consider intervals between occupied seats.
We also need to efficiently insert and remove seats as students enter and leave, which suggests using a data structure that supports fast insertion, deletion, and neighbor queries.
Let's break down the algorithm step by step:
seat()
):
leave(p)
):
p
from our data structure.
Let's walk through an example with N = 10
and a sequence of operations:
seats = [0]
)seats = [0,9]
)seats = [0,4,9]
)seats = [0,2,4,9]
)seats = [0,2,9]
)seats = [0,2,5,9]
)At each step, we only need to consider the largest gaps between occupied seats and the edges.
Brute-Force Approach:
seat()
, check every seat for the minimum distance to any other occupied seat. This is O(N^2) per operation, which is too slow for large N.seat()
and leave()
operation involves:
seat()
(since we must check all pairs).Further Optimizations: For very high performance, a priority queue of intervals can be used to achieve O(log n) seat assignment, but the above approach is sufficient for most constraints.
The Exam Room problem is a great exercise in interval management and greedy algorithms. By keeping track of occupied seats in a sorted data structure, we can always find the largest available gap for a new student efficiently. The solution elegantly balances between simplicity and efficiency, making use of sorted collections and careful interval analysis to maintain the optimal seating arrangement after every operation.