直接上题目:
你有 k 个服务器,编号为 0 到 k-1 ,它们可以同时处理多个请求组。每个服务器有无穷的计算能力但是 不能同时处理超过一个请求 。请求分配到服务器的规则如下:
第 i (序号从 0 开始)个请求到达。
如果所有服务器都已被占据,那么该请求被舍弃(完全不处理)。
如果第 (i % k) 个服务器空闲,那么对应服务器会处理该请求。
否则,将请求安排给下一个空闲的服务器(服务器构成一个环,必要的话可能从第 0 个服务器开始继续找下一个空闲的服务器)。比方说,如果第 i 个服务器在忙,那么会查看第 (i+1) 个服务器,第 (i+2) 个服务器等等。
给你一个 严格递增 的正整数数组 arrival ,表示第 i 个任务的到达时间,和另一个数组 load ,其中 load[i] 表示第 i 个请求的工作量(也就是服务器完成它所需要的时间)。你的任务是找到 最繁忙的服务器 。最繁忙定义为一个服务器处理的请求数是所有服务器里最多的。
请你返回包含所有 最繁忙服务器 序号的列表,你可以以任意顺序返回这个列表。
这是很典型的维护优先队列解决问题,自己思考没解决出来,看的官方题解,总结一下思路:
首先维护一个available有序集合,用来放置可用的服务器编号,之所以是集合,因为集合里没有重复元素,所以可以在一开始初始化;
其次维护一个优先队列busy,用来放置服务器编号和结束时间;
再维护一个list用来记录服务器使用次数
思路是这样,先循环到达数组arrival,当busy不为空的时候,判断busy里最先空闲的服务器是否小于等于当前到达时间,如果是的话,将busy最顶端服务器编号推出,放到available里,刚开始初始化available是因为刚开始所有服务器都是可用的,所以都放进去,而且available是集合,不用考虑重复元素;如果available里没有可用的服务器,那就跳过此次循环,反之先取出i % k的服务器,然后将list对应编号++。此时将使用的服务器编号和耗时放到busy里,耗时是arrival[i] + load[i];
class Solution {
public List busiestServers(int k, int[] arrival, int[] load) {
int list[] = new int[k];
// for(int i = 0 ; i < k ; i++){
// list[i]=0;
// }
PriorityQueue busy = new PriorityQueue<>((a,b) -> a[0] - b[0]);
TreeSet available = new TreeSet<>();
for(int i = 0 ; i < k ; i++){
available.add(i);
}
for(int i = 0 ; i < arrival.length; i++){
while(!busy.isEmpty() && busy.peek()[0] <= arrival[i]){
available.add(busy.poll()[1]); //将可用的生产线取出来放到available里
}
if(available.isEmpty()){
continue;
}
if(available.ceiling(i % k ) != null){
int temp = available.ceiling(i % k );
available.remove(temp);
list[temp]++;
busy.offer(new int[]{arrival[i] + load[i], temp});
}else{
int temp = available.pollFirst();
list[temp]++;
busy.offer(new int[]{arrival[i] + load[i], temp});
}
}
int max = Arrays.stream(list).max().getAsInt();
List ret = new ArrayList();
for(int i = 0 ; i < k ; i++){
if(list[i] == max){
ret.add(i);
}
}
return ret;
}
}
今天学习入门了线段树,也是同样的题目,可以用线段树来做
先贴下代码,是转载的题解里的线段树代码
public class Node{
int end;
int l,r;
Node left = null;
Node right = null;
public Node(int l , int r){
this.l = l;
this.r = r;
}
}
import java.util.ArrayList;
import java.util.List;
public class ttt {
public static void main(String[] args){
int k = 3;
int[] arrival = new int[]{1,2,3,4,5};
int[] load = new int[]{5,2,3,3,3};
Node root = buildTree(0,k-1);
int[] cnt = new int[k];
int max= 0;
for(int i = 0 ; i < arrival.length; i++){
if(arrival[i] < root.end){
continue;
}
int pos = i % k;
int x = query(root,pos, k-1, arrival[i]);
if(x == -1){
x = query(root,0,pos-1,arrival[i]);
}
cnt[x]++;
max = Math.max(max,cnt[x]);
update(root,x, arrival[i] + load[i]);
}
List list = new ArrayList<>();
for(int i = 0; i < k; i++){
if(cnt[i] == max){
list.add(i);
}
}
System.out.println(list);
}
private static Node buildTree(int left, int right){
Node node = new Node(left,right);
if(left == right){
return node;
}
int mid = (left + right) >>> 1;
node.left = buildTree(left, mid);
node.right = buildTree(mid+1, right);
return node;
}
private static void update (Node root, int x ,int end){
if(root.l == root.r){
root.end = end;
return;
}
int mid = (root.l + root.r) >>> 1;
if(x <= mid){
update(root.left, x, end);
}else{
update(root.right, x, end);
}
root.end = Math.min(root.left.end,root.right.end);
}
private static 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) >>>1;
int val = -1;
//访问左子树
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;
}
}
我把题解的代码重新写了下,可以在wecode运行,调试模式运行,可以一步步来看。
大概说下思路:
当消息1到达时:
当消息2到达时:
可以看出来,线段树是维护一个区间的信息,比如上面这个题目,就是维护0到2这个区间的信息,然后线段树其实就是每个叶子节点其实就是单独一个点,所有叶子节点加起来就构成了根节点的区间。在上面这个题目,0到2区间,其实也就是服务器编号,比如,根节点的0到2,代表着在0到2服务器里,目前最早结束时间是0,也就是end=0;它的左子树是0到1,end=4,说明服务器0到1中,最早结束时间是4;右子树区间是2到2,最早结束时间是0;在上面这个树里,父节点的end是左右子树的end的最小值,(有一点优先队列的意思);
再看到代码:
buildTree代码
就是利用二分思想,递归建树,没啥好说的,递归建树,最重要的是终止条件,当l==r的时候终止,也就是只到一个点
update代码:
也是利用二分思想递归更新节点,终止条件是一直找到叶子节点,将end=end;这里说一下为什么是找到叶子节点,这是要找的目标节点是一个点,并不是一个区间,那肯定就是在某个叶子节点上;二分递归查找最终就只会到叶子节点上,最后再把父节点的end取两个子树的最小值;
query代码:
这里也是用的二分递归,先查左子树,再查右子树(之所以先左后右,我们查的是一个区间里是否有符合条件的服务器,先左后右的意思其实就是在一个区间从左往右);这里有一个关键点是,因为题目是有给区间范围的,每次先查i%k,再依次查找;所以query传进去的 l 和 r 都是当前服务器查找的范围,而query里的mid是当前节点的l + r >>>1 ,判断l<=mid 和r > mid 其实就是在判断给出的区间是否在左右子树的区间里