Medium
题目描述
有一场聚会,n 个朋友编号从 0 到 n - 1。聚会上有无限个椅子,编号从 0 到无穷大。当一个朋友到达聚会时,他们会坐在编号最小的未被占据的椅子上。
例如,如果椅子 0、1 和 5 被占据,当一个朋友来时,他们会坐在椅子编号 2 上。
当一个朋友离开聚会时,他们的椅子在离开的那一刻变为未被占据。如果另一个朋友在同一时刻到达,他们可以坐在那个椅子上。
给你一个下标从 0 开始的二维整数数组 times,其中 times[i] = [arrival_i, leaving_i] 表示第 i 个朋友的到达和离开时间,以及一个整数 targetFriend。所有到达时间都是不同的。
返回编号为 targetFriend 的朋友将坐在的椅子编号。
示例 1:
输入:times = [[1,4],[2,3],[4,6]], targetFriend = 1
输出:1
解释:
- 朋友 0 在时间 1 到达,坐在椅子 0 上。
- 朋友 1 在时间 2 到达,坐在椅子 1 上。
- 朋友 1 在时间 3 离开,椅子 1 变为空闲。
- 朋友 0 在时间 4 离开,椅子 0 变为空闲。
- 朋友 2 在时间 4 到达,坐在椅子 0 上。
由于朋友 1 坐在椅子 1 上,我们返回 1。
示例 2:
输入:times = [[3,10],[1,5],[2,6]], targetFriend = 0
输出:2
解释:
- 朋友 1 在时间 1 到达,坐在椅子 0 上。
- 朋友 2 在时间 2 到达,坐在椅子 1 上。
- 朋友 0 在时间 3 到达,坐在椅子 2 上。
- 朋友 1 在时间 5 离开,椅子 0 变为空闲。
- 朋友 2 在时间 6 离开,椅子 1 变为空闲。
- 朋友 0 在时间 10 离开,椅子 2 变为空闲。
由于朋友 0 坐在椅子 2 上,我们返回 2。
提示:
- n == times.length
- 2 <= n <= 10^4
- times[i].length == 2
- 1 <= arrival_i < leaving_i <= 10^5
- 0 <= targetFriend <= n - 1
- 每个 arrival_i 时间都是不同的。
解题思路
这是一道模拟题,需要处理椅子的分配和释放过程。核心思路是使用两个优先队列来管理椅子状态。
解题思路:
- 事件驱动模拟:将所有朋友的到达和离开看作事件,按时间顺序处理
- 椅子管理:使用最小堆维护可用椅子编号,确保总是分配最小编号的椅子
- 离开事件处理:使用另一个最小堆记录离开事件,当到达某个时间点时,先处理所有在该时间点离开的朋友
具体步骤:
- 创建一个包含朋友编号的事件数组,按到达时间排序
- 使用
availableChairs最小堆维护可用椅子编号 - 使用
leavingQueue最小堆维护离开事件 (离开时间, 椅子编号) - 对每个到达事件:
- 先处理当前时间的所有离开事件,释放椅子
- 分配最小可用椅子给当前朋友
- 如果是目标朋友,返回椅子编号
时间复杂度优化: 通过优先队列,椅子分配和释放都是 O(log n) 操作,总体效率较高。
代码实现
class Solution {
public:
int smallestChair(vector<vector<int>>& times, int targetFriend) {
vector<pair<int, int>> events; // {arrival_time, friend_id}
for (int i = 0; i < times.size(); i++) {
events.push_back({times[i][0], i});
}
sort(events.begin(), events.end());
priority_queue<int, vector<int>, greater<int>> availableChairs; // min heap
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> leavingQueue; // {leave_time, chair}
// Initialize available chairs
for (int i = 0; i < times.size(); i++) {
availableChairs.push(i);
}
for (auto& event : events) {
int arrivalTime = event.first;
int friendId = event.second;
// Process all friends leaving at or before current arrival time
while (!leavingQueue.empty() && leavingQueue.top().first <= arrivalTime) {
availableChairs.push(leavingQueue.top().second);
leavingQueue.pop();
}
// Assign chair to current friend
int chair = availableChairs.top();
availableChairs.pop();
if (friendId == targetFriend) {
return chair;
}
// Add leaving event for this friend
leavingQueue.push({times[friendId][1], chair});
}
return -1;
}
};
class Solution:
def smallestChair(self, times: List[List[int]], targetFriend: int) -> int:
import heapq
events = [(times[i][0], i) for i in range(len(times))]
events.sort()
available_chairs = list(range(len(times))) # min heap
heapq.heapify(available_chairs)
leaving_queue = [] # (leave_time, chair)
for arrival_time, friend_id in events:
# Process all friends leaving at or before current arrival time
while leaving_queue and leaving_queue[0][0] <= arrival_time:
_, chair = heapq.heappop(leaving_queue)
heapq.heappush(available_chairs, chair)
# Assign chair to current friend
chair = heapq.heappop(available_chairs)
if friend_id == targetFriend:
return chair
# Add leaving event for this friend
heapq.heappush(leaving_queue, (times[friend_id][1], chair))
return -1
public class Solution {
public int SmallestChair(int[][] times, int targetFriend) {
var events = new List<(int arrival, int friendId)>();
for (int i = 0; i < times.Length; i++) {
events.Add((times[i][0], i));
}
events.Sort();
var availableChairs = new PriorityQueue<int, int>();
var leavingQueue = new PriorityQueue<(int leaveTime, int chair), int>();
// Initialize available chairs
for (int i = 0; i < times.Length; i++) {
availableChairs.Enqueue(i, i);
}
foreach (var (arrivalTime, friendId) in events) {
// Process all friends leaving at or before current arrival time
while (leavingQueue.Count > 0 && leavingQueue.Peek().leaveTime <= arrivalTime) {
var (_, chair) = leavingQueue.Dequeue();
availableChairs.Enqueue(chair, chair);
}
// Assign chair to current friend
int assignedChair = availableChairs.Dequeue();
if (friendId == targetFriend) {
return assignedChair;
}
// Add leaving event for this friend
leavingQueue.Enqueue((times[friendId][1], assignedChair), times[friendId][1]);
}
return -1;
}
}
var smallestChair = function(times, targetFriend) {
const events = [];
for (let i = 0; i < times.length; i++) {
events.push([times[i][0], i, 'arrive']);
events.push([times[i][1], i, 'leave']);
}
events.sort((a, b) => {
if (a[0] === b[0]) {
return a[2] === 'leave' ? -1 : 1;
}
return a[0] - b[0];
});
const availableChairs = [];
const occupiedChairs = new Map();
let nextChair = 0;
for (const [time, friend, action] of events) {
if (action === 'leave') {
const chair = occupiedChairs.get(friend);
occupiedChairs.delete(friend);
availableChairs.push(chair);
availableChairs.sort((a, b) => a - b);
} else {
let chair;
if (availableChairs.length > 0) {
chair = availableChairs.shift();
} else {
chair = nextChair++;
}
occupiedChairs.set(friend, chair);
if (friend === targetFriend) {
return chair;
}
}
}
return -1;
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n log n) | 排序事件需要 O(n log n),每次椅子分配和释放操作需要 O(log n),总共 n 次操作 |
| 空间复杂度 | O(n) | 需要存储事件列表、可用椅子堆和离开事件堆,都是 O(n) 空间 |