每日一题做题记录,参考官方和三叶的题解 |
题目要求
思路一:有序集合+优先队列
模拟就完了,用哈希表统计每个server处理的请求数,因为数据范围在
[
1
,
1
0
5
]
[1,10^5]
[1,105],所以可以开一个静态数组cnt
替代哈希表,同时维护一个
m
a
x
max
max值表示cnt
中的最大数,也就是处理的最多请求数。
【下面开始高能数据结构】
用优先队列维护忙碌池
,一个包含编号和请求结束时间的二元组
(
i
d
x
,
e
n
d
T
i
m
e
)
(idx, endTime)
(idx,endTime),那么请求到来时,只要对比到来时间和
e
n
d
T
i
m
e
endTime
endTime即可判断是否能够处理。
由于请求分配规则是优先取大于等于 i % k i \% k i%k的最小值,若取不到则取大于等于 0 0 0的最小值。因此
空闲池
最好是支持二分的有序集合,则使用基于红黑树的TreeSet结构。
【这段属于是懂了但没完全懂,二分……是为了啥呢】
Java
class Solution {
static int N = 100001; //数据范围
static int[] cnt = new int[N]; //统计每个server处理的请求数
public List<Integer> busiestServers(int k, int[] arrival, int[] load) {
Arrays.fill(cnt, 0);
int max = 0;
Map<Integer, Integer> map = new HashMap<>();
PriorityQueue<int[]> busy = new PriorityQueue<>((a,b) -> a[1] - b[1]); //优先队列(小顶堆)(编号, 结束时间)
TreeSet<Integer> free = new TreeSet<>(); //红黑树
for(int i = 0; i < k; i++)
free.add(i);
for(int i = 0; i < arrival.length; i++) {
int sta = arrival[i], end = sta + load[i]; //请求开始与结束时间
while(!busy.isEmpty() && busy.peek()[1] <= sta) //上一请求结束时间 < 下一请求开启时间
free.add(busy.poll()[0]);
Integer u = free.ceiling(i % k); //处理该请求的server编号
if(u == null)
u = free.ceiling(0);
//取不到
if(u == null)
continue; //丢弃该请求
//取到了,更新空闲池和忙碌池
free.remove(u);
busy.add(new int[]{u, end});
max = Math.max(max, ++cnt[u]);
}
List<Integer> res = new ArrayList<>();
for(int i = 0; i < k; i++)
if(cnt[i] == max) //筛选结果
res.add(i);
return res;
}
}
- 时间复杂度:
O
(
(
k
+
n
)
log
k
)
O((k+n)\log k)
O((k+n)logk),初始将server存入
f
r
e
e
free
free复杂度为
O
(
k
log
k
)
O(k\log k)
O(klogk),每个请求取server时调用
ceiling
(最多两次)的复杂度为 O ( log k ) O(\log k) O(logk),共 n n n个请求即 O ( n log k ) O(n\log k) O(nlogk) - 空间复杂度: O ( k ) O(k) O(k),每个容器最大都是 k k k
TreeSet
- 学习参考链接
- 一个有序的Set集合,基于TreeMap实现,支持自然排序或根据Comparator排序。
- 它基本操作比之前学的
HashSet
慢,但是有序且易于导航(即内部实现了SortedSet和NavigableSet接口)。 - 在此用于实现一个红黑树(自平衡二叉查找树),就只用了很简单的
ceiling
。
方法 | 功能 |
---|---|
add() | 添加元素 |
remove() | 删除元素 |
ceiling(key) | 返回大于等于key的最小元素 |
PriorityQueue
- 学习参考链接
- 无界的基于优先级的队列,支持自然排序或根据Comparator排序,头最小尾最大。(无界所以线程不安全)
- 此处初始化时是用lambda定义pair排序逻辑,按上面代码里会实现小顶堆(堆顶为最小值),要改变的话括号里改为
(a, b) -> b - a)
即可。
方法 | 功能 |
---|---|
add() | 添加元素 |
peek() | 返回头元素(最小值) |
poll() | 删除并返回头元素(最小值) |
C++
class Solution {
public:
vector<int> busiestServers(int k, vector<int>& arrival, vector<int>& load) {
vector<int> cnt(k); //统计每个server处理的请求数
set<int> free;
for(int i = 0; i < k; i++)
free.insert(i);
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> busy; //小顶堆,(编号, 结束时间)
for(int i = 0; i < arrival.size(); i++) {
int sta = arrival[i], end = sta + load[i]; //请求开始与结束时间
while(!busy.empty() && busy.top().first <= sta) { //上一请求结束时间 < 下一请求开启时间
free.insert(busy.top().second);
busy.pop();
}
auto u = free.lower_bound(i % k); //处理该请求的server编号
if(u == free.end())
u = free.begin();
//取不到
if(u == free.end())
continue; //丢弃该请求
//取到了,更新空闲池和忙碌池
cnt[*u]++;
busy.emplace(end, *u);
free.erase(u);
}
//取最大请求处理数并筛选结果
int max = *max_element(cnt.begin(), cnt.end());
vector<int> res;
for(int i = 0; i < k; i++)
if(cnt[i] == max)
res.push_back(i);
return res;
}
};
- 时间复杂度: O ( ( k + n ) log k ) O((k+n)\log k) O((k+n)logk)
- 空间复杂度: O ( k ) O(k) O(k)
set
- 学习参考链接
- 关联式容器,键与值需相等,元素需各不相同,会自动根据键值大小进行排序。
- 因返回值均为双向迭代器,不能与空值NULL作比较,则判断是否指向
end()
作为替代。
方法 | 功能 |
---|---|
insert() | 插入新元素 |
lower_bound(key) | 返回指向大于等于key的最小元素的双向迭代器 |
begin() | 返回指向容器中的第一个元素的双向迭代器 |
end() | 返回指向容器中的最后一个元素的双向迭代器 |
erase(key) | 删除容器中的key元素 |
priority_queue
- 学习参考链接
- 有序排列的队列容器,默认队头为最大的元素
方法 | 功能 |
---|---|
greater<> | 使最小元素排在前面,队头为最小 |
top() | 返回第一个元素的引用 |
pop() | 删除第一个元素 |
emplace() | 可理解为添加新元素 |
思路二:双优先队列
两个池都用优先队列实现,其他一样,代码就不搞了。
时空复杂度应该也是一样的,有差异的话就是把server存入
f
r
e
e
free
free的地方可能有问题。
总结
思路就是模拟,难在数据结构的处理上。
欢迎指正与讨论! |