座位预约管理系统

标签: 设计 堆(优先队列)

难度: Medium

请你设计一个管理 n 个座位预约的系统,座位编号从 1 到 n 。

请你实现 SeatManager 类:

  • SeatManager(int n) 初始化一个 SeatManager 对象,它管理从 1 到 n 编号的 n 个座位。所有座位初始都是可预约的。
  • int reserve() 返回可以预约座位的 最小编号 ,此座位变为不可预约。
  • void unreserve(int seatNumber) 将给定编号 seatNumber 对应的座位变成可以预约。

 

示例 1:

输入:
["SeatManager", "reserve", "reserve", "unreserve", "reserve", "reserve", "reserve", "reserve", "unreserve"]
[[5], [], [], [2], [], [], [], [], [5]]
输出:
[null, 1, 2, null, 2, 3, 4, 5, null]

解释:
SeatManager seatManager = new SeatManager(5); // 初始化 SeatManager ,有 5 个座位。
seatManager.reserve();    // 所有座位都可以预约,所以返回最小编号的座位,也就是 1 。
seatManager.reserve();    // 可以预约的座位为 [2,3,4,5] ,返回最小编号的座位,也就是 2 。
seatManager.unreserve(2); // 将座位 2 变为可以预约,现在可预约的座位为 [2,3,4,5] 。
seatManager.reserve();    // 可以预约的座位为 [2,3,4,5] ,返回最小编号的座位,也就是 2 。
seatManager.reserve();    // 可以预约的座位为 [3,4,5] ,返回最小编号的座位,也就是 3 。
seatManager.reserve();    // 可以预约的座位为 [4,5] ,返回最小编号的座位,也就是 4 。
seatManager.reserve();    // 唯一可以预约的是座位 5 ,所以返回 5 。
seatManager.unreserve(5); // 将座位 5 变为可以预约,现在可预约的座位为 [5] 。

 

提示:

  • 1 <= n <= 105
  • 1 <= seatNumber <= n
  • 每一次对 reserve 的调用,题目保证至少存在一个可以预约的座位。
  • 每一次对 unreserve 的调用,题目保证 seatNumber 在调用函数前都是被预约状态。
  • 对 reserve 和 unreserve 的调用 总共 不超过 105 次。

Submission

运行时间: 328 ms

内存: 43.6 MB

import heapq
class SeatManager:

    def __init__(self, n: int):
        self.minV=1
        self.unr = []
    def reserve(self) -> int:
        if len(self.unr)==0:
            self.minV+=1
            return self.minV-1
        else:
            ret = heapq.heappop(self.unr)
            return ret

    def unreserve(self, s: int) -> None:
        heapq.heappush(self.unr, s)


# Your SeatManager object will be instantiated and called as such:
# obj = SeatManager(n)
# param_1 = obj.reserve()
# obj.unreserve(seatNumber)

Explain

该题解通过使用最小堆(由Python的heapq库提供)来管理所有已释放的座位编号。对于未释放的座位,通过一个变量minV跟踪下一个可预约的最小座位编号。当调用reserve()时,如果堆中有已释放的座位,它将返回并弹出堆中的最小元素,即最小编号的座位;如果堆为空,则返回minV当前值,并将minV增加1。当调用unreserve(seatNumber)时,将座位号seatNumber添加到堆中,以便该座位号在将来被重新预约。

时间复杂度: O(log n)

空间复杂度: O(n)

import heapq

class SeatManager:

    def __init__(self, n: int):
        self.minV = 1  # 追踪下一个可预约的最小座位编号
        self.unr = []  # 最小堆维护已释放的座位编号

    def reserve(self) -> int:
        if len(self.unr) == 0:  # 如果没有已释放的座位
            self.minV += 1
            return self.minV - 1  # 返回当前最小编号并更新minV
        else:
            return heapq.heappop(self.unr)  # 返回并移除堆中的最小元素

    def unreserve(self, s: int) -> None:
        heapq.heappush(self.unr, s)  # 将座位号添加到堆中

Explore

最小堆被选择用来维护已释放的座位编号是因为它能够高效地支持所需的操作。最小堆能够在常数时间内提供最小元素的访问,并且能在对数时间内完成元素的插入和删除操作。使用数组或链表虽然可以实现相同的功能,但在找到最小元素时,他们需要O(n)时间复杂度,这在性能上不如使用最小堆。

如果连续调用`unreserve`方法将同一个座位编号加入最小堆多次,这将导致堆中存在重复的座位编号。在随后调用`reserve`方法时,这些重复的编号会被逐一返回,这可能会导致预约系统的混乱,因为同一个座位可能被认为多次预约。在题解提供的代码中,并没有处理重复座位编号的机制,这是一个需要改进的地方。

是的,这种做法保证了每次都能返回最小的可预约的座位编号。因为最小堆的性质是保证堆顶(即第一个元素)始终是堆中最小的元素。因此,当最小堆不为空时,从堆中弹出最小元素确实是当前所有已释放的座位中编号最小的一个。

是的,在实际应用中,确实需要考虑对座位的有效性进行验证。添加额外的验证可以防止错误的座位编号被加入到堆中,如编号超出有效范围等情况。这种验证有助于保护系统的健壮性,防止数据错误导致的问题。