In a classroom with n seats, numbered from 0 to n-1, a student must choose the seat with the largest minimum distance to any other occupied seat. If multiple seats satisfy this condition, choose the seat with the smallest number.
Implement a class Room with the following method:
在一个教室里有 n 个座位, 编号为 0,1,2,…,n−1. 当有同学进教室时,他必须选择距离最近的同学之间的距离最大的位置坐下. 如果有多个位置满足条件, 选择编号最小的那个.
实现一个类Room, 包含以下方法:
1 2 3 4 5 6 7
classRoom: def__init__(self, n: int): ... defseat(self) -> int: # Return the index of the selected position 返回选中的位置 ... defleave(self, pos: int): # The person leave the classroom at position pos pos位置上的人离开教室 ...
left = Interval(start, seat, self.N) right = Interval(seat, end, self.N) self.start_map[start] = left self.end_map[seat] = left self.start_map[seat] = right self.end_map[end] = right heapq.heappush(self.heap, left) heapq.heappush(self.heap, right)
return seat
defleave(self, p): self.seated.remove(p)
left = self.end_map.get(p) right = self.start_map.get(p)
if left: delself.start_map[left.start] delself.end_map[left.end] if right: delself.start_map[right.start] delself.end_map[right.end]
new_start = left.start if left else p new_end = right.end if right else p merged = Interval(new_start, new_end, self.N) self.start_map[new_start] = merged self.end_map[new_end] = merged heapq.heappush(self.heap, merged)
The key data structures for this problem are a heap and a dictionary. Initially, analyze the problem. Each time the seat() function is called, a seat must be found that offers the “largest minimum distance between neighboring students.” Although this constraint sounds complicated, it can be translated to: “Find the longest continuous unoccupied seat segment.” For each continuous unoccupied segment, the middle position is the position within that segment that offers the “largest minimum distance between neighboring students.” Thus, the distance is related only to the length of the current segment (specifically, half of the segment length, except for the initial and final segments, where the nearest distance is exactly twice the segment length).
Therefore, the problem’s requirements become clear:
Each call to seat() finds and returns the middle position of the longest continuous unoccupied segment. After returning, the segment is split into two halves at that position.
Each call to leave() removes the two segments separated by the leave() position and re-merges them.
所以这题的需求就变得很清晰了:
每次调用seat就把所有区间里最长的拿出来, 返回中点后把区间从那个位置掰成两半
每次调用leave就把leave的位置所区分的两个区间都去掉, 重新拼接起来
The first function can be easily thought of using a heap data structure, but the second function cannot be implemented solely with a heap; it requires a dictionary for additional implementation.
classInterval: def__init__(self, start, end, N): self.start = start self.end = end self.N = N
defdist(self): # No need need to divide by 2 because two heads nobody 因为两头没人所以长度不需要除以2 ifself.start == -1: returnself.end ifself.end == self.N: returnself.N - 1 - self.start return (self.end - self.start) // 2
First, design an Interval class to represent each continuous empty seat segment. The dist() function is used to calculate the maximum distance to the nearest classmate within the current segment. __lt__(self, other) is for calling the Python heappush and heappop functions because Python’s heap operations are a min-heap by default, so the definition of __lt__(self, other) is actually the opposite of the actual definition.
Then, initialize the Room class, where start_map and end_map respectively store Interval objects starting/ending at key points. seated is used to record the set of occupied seats.
defseat(self): whileself.heap: interval = heapq.heappop(self.heap) start, end = interval.start, interval.end if start inself.start_map andself.start_map[start] == interval: # Verify the interval is valid 验证这个区间是存在的 return seat else: continue break else: return -1 if start == -1: seat = 0 elif end == self.N: seat = self.N - 1 else: seat = (start + end) // 2
self.seated.add(seat)
delself.start_map[start] delself.end_map[end]
left = Interval(start, seat, self.N) right = Interval(seat, end, self.N) self.start_map[start] = left self.end_map[seat] = left self.start_map[seat] = right self.end_map[end] = right heapq.heappush(self.heap, left) heapq.heappush(self.heap, right)
First, take the largest interval from the heap, and simultaneously use start_map and end_map to determine whether the current interval actually exists (explained in the leave function). After taking out the largest interval, determine the position where the seat should be placed, then split the interval according to the seat’s position and update start_map, end_map, and heap.
left = self.end_map.get(p) right = self.start_map.get(p)
if left: delself.start_map[left.start] delself.end_map[left.end] if right: delself.start_map[right.start] delself.end_map[right.end]
new_start = left.start if left else p new_end = right.end if right else p merged = Interval(new_start, new_end, self.N) self.start_map[new_start] = merged self.end_map[new_end] = merged heapq.heappush(self.heap, merged)
First, find the two segments to be merged through start_map and end_map. Then merge these two segments into a new interval and add it to the heap. Theoretically, the algorithm should remove the original two intervals from the heap and then add the new interval to the heap. However, the heap does not natively support this operation, so simply ignore it here because the seat() function implements a verification process that checks whether the current interval corresponds to the intervals in start_map and end_map. If it doesn’t, it means that this interval no longer exists, so the algorithm will simply ignore it. This ensures the correctness of the answer even if the original interval is not deleted.
All heap operations in the algorithm have a time complexity of O(log n), so the total time complexity is O(n log n). Here n is number of operations.
Conclusion 总结
This question is a moderately difficult data structure problem that involves slight modifications and optimizations to the original heap operations. It requires a good grasp of basic data structure operations and implementations, and is the type of problem that is easy to come up with an idea for but requires paying attention to many details in the actual implementation.