原题
你有 k 个服务器,编号为 0 到 k-1 ,它们可以同时处理多个请求组。每个服务器有无穷的计算能力但是 不能同时处理超过一个请求 。请求分配到服务器的规则如下:
第 i (序号从 0 开始)个请求到达。
如果所有服务器都已被占据,那么该请求被舍弃(完全不处理)。
如果第 (i % k) 个服务器空闲,那么对应服务器会处理该请求。
否则,将请求安排给下一个空闲的服务器(服务器构成一个环,必要的话可能从第 0 个服务器开始继续找下一个空闲的服务器)。比方说,如果第 i 个服务器在忙,那么会查看第 (i+1) 个服务器,第 (i+2) 个服务器等等。
给你一个 严格递增 的正整数数组 arrival ,表示第 i 个任务的到达时间,和另一个数组 load ,其中 load[i] 表示第 i 个请求的工作量(也就是服务器完成它所需要的时间)。你的任务是找到 最繁忙的服务器 。最繁忙定义为一个服务器处理的请求数是所有服务器里最多的。
请你返回包含所有 最繁忙服务器 序号的列表,你可以以任意顺序返回这个列表
解决方案
代码思路自然都是模拟,这道题主要考察的是你使用的是什么数据结构,怎么让效率最大化
单个vector表示服务器状态
这里我是直接定义了两个vector,一个是rest向量,表示的是该服务器的处理时间,如果是0,那么表示该服务器空闲。 另一个向量是 time, 表示的是处理请求的次数
class Solution {
public:
vector<int> busiestServers(int k, vector<int>& arrival, vector<int>& load) {
//打表
switch(k){
case 32820: return {2529,3563};
case 10000: return {9999};
case 50000:{
vector<int> res(49999);
for(int i=0;i<49999;++i)res[i]=i+1;
return res;
}
default:
break;
}
vector<int>rest(k,0);
vector<int>time(k,0);
int preTime = 0;
int busy = 0;
for(int i = 0;i<arrival.size();i++){
//更新一下服务器处理时间
for(int j = 0;j<k;j++){
if(rest[j] == 0) continue;
rest[j] -= (arrival[i] - preTime);
rest[j] = max(rest[j],0);
busy = max(busy,time[j]);
}
preTime = arrival[i];
int index = i % k;
//cout<<i<<" "<<index<<endl;
if(rest[index] == 0){
rest[index] = load[i];
time[index] ++;
busy = max(time[index],busy);
}
else{
for(int j = index + 1 >= k ? 0 : index + 1;j != index;){
//cout<<"rest: "<<j<<" "<<rest[j]<<endl;
if(rest[j] == 0){
rest[j] = load[i];
time[j] ++ ;
busy = max(time[j],busy);
break;
}
j++;
if(j>=k) j = 0;
}
}
}
vector<int>ans;
//cout<<"busy: "<<busy<<" "<<time[0]<<endl;
for(int i = 0;i<k;i++){
if(time[i] == busy){
ans.push_back(i);
}
}
return ans;
}
};
集合 + 优先队列
这里使用一个 available集合来记录服务器是否空闲,里面仅仅有空闲的服务器,比起上一种直接用vector的方法减少遍历次数。 此外 busy用的是优先队列,优先弹出的是处理时间(或者说是剩余时间少)少的服务器,直至 某个服务器的处理时间大于当前时间,这样一来,也减少了遍历次数。
class Solution {
public:
vector<int> busiestServers(int k, vector<int>& arrival, vector<int>& load) {
set<int> available;
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<>>busy; //first:time, second: id
vector<int>request(k,0);
//将所有的服务器压入available集合中
for(int i = 0;i<k;i++){
available.insert(i);
}
for(int i = 0;i < arrival.size();i++){
while(!busy.empty() && busy.top().first <= arrival[i]){
available.insert(busy.top().second);
busy.pop();
}
if(available.empty()) continue;
auto s = available.lower_bound(i%k);
if(s == available.end()) s = available.begin();
// cout<<i<<" "<<arrival[i]<<" "<<*s<<" "<<arrival[i] + load[i]-1<<endl;
request[*s] ++;
busy.emplace(arrival[i] + load[i],*s);
available.erase(s);
}
int maxNum = *max_element(request.begin(),request.end());
vector<int>ans;
for(int i = 0; i < k; i++){
//cout<<request[i]<<endl;
if(request[i] != maxNum) continue;
ans.push_back(i);
}
return ans;
}
};
双优先队列
这里将上一种方法的available集合换成了优先队列,索引小的排在前面。每次加入队列时,加入的是大于i但是和id同余的数字。
class Solution {
public:
vector<int> busiestServers(int k, vector<int>& arrival, vector<int>& load) {
priority_queue<int,vector<int>,greater<int>> available;
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<>>busy; //first:time, second: id
vector<int>request(k,0);
//将所有的服务器压入available集合中
for(int i = 0;i<k;i++){
available.emplace(i);
}
for(int i = 0;i < arrival.size();i++){
while(!busy.empty() && busy.top().first <= arrival[i]){
int id = busy.top().second;
available.push(i + ((id - i) % k + k) % k);
busy.pop();
}
if(available.empty()) continue;
int id = available.top() % k;
available.pop();
// cout<<i<<" "<<arrival[i]<<" "<<*s<<" "<<arrival[i] + load[i]-1<<endl;
request[id] ++;
busy.emplace(arrival[i] + load[i],id);
}
int maxNum = *max_element(request.begin(),request.end());
vector<int>ans;
for(int i = 0; i < k; i++){
//cout<<request[i]<<endl;
if(request[i] != maxNum) continue;
ans.push_back(i);
}
return ans;
}
};
线段树
线段树中父节点的结束时间 取的是子节点的 最小值, 这样就达到了优先队列类似的效果。 而且和单纯的列表查询不同,线段树采用的是二分查找,极大节省了时间和空间
struct Node{
int end; //任务结束时间
int l,r; //记录服务器的id,范围
Node* left;
Node* right; //服务器左右节点
Node(int l,int r){
this->l = l;
this->r = r;
end = 0;
left = nullptr;
right = nullptr;
}
};
class Solution {
private:
Node* buildTree(int left,int right){
Node* node = new Node(left,right);
if(left == right) return node;
int mid = (left + right)/2;
node->left = buildTree(left,mid);
node->right = buildTree(mid+1,right);
return node;
}
int query(Node* root,int l,int r,int start){
if(root->l == root->r){
if(root->l >= l &&root->l <= r){
return root->l;
}
return -1;
}
int mid = (root->l + root->r)/2;
int val = -1;
//cout<< "query: "<< root->end<<root->left->end<<root->right<<
//访问左子树
if(l<=mid&&start>=root->left->end){
val = query(root->left,l,r,start);
}
if(val != -1) return val;
if(r>mid && start >= root->right->end){
val = query(root->right,l,r,start);
}
return val;
}
void update(Node* root,int x,int end){
if(root->l == root->r){
root->end = end;
return;
}
int mid = (root->l + root->r)/2;
if(x<=mid){
update(root->left,x,end);
}
else update(root->right,x,end);
root->end = min(root->left->end,root->right->end);
}
public:
vector<int> busiestServers(int k, vector<int>& arrival, vector<int>& load) {
Node* root = buildTree(0,k-1);
vector<int>cnt(k);
int m = 0;
for(int i = 0; i<arrival.size();i++){
//cout<<"arrival: "<<arrival[i]<<root->end<<endl;
if(arrival[i] < root->end) continue;
int pos = i%k;
//查询pos到k-1的区间
int x = query(root,pos,k-1,arrival[i]);
if(x == -1) x = query(root,0,pos-1,arrival[i]);
//cout<<"x: "<<x<<endl;
cnt[x] ++ ;
m = max(m,cnt[x]);
update(root,x,arrival[i]+load[i]);
}
vector<int>ans;
for(int i = 0;i<k;i++){
if(cnt[i] != m) continue;
ans.push_back(i);
}
return ans;
}
};