java优先队列 leetcode1606

直接上题目:

你有 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到达时:

java优先队列 leetcode1606_第1张图片

当消息2到达时:

java优先队列 leetcode1606_第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 其实就是在判断给出的区间是否在左右子树的区间里

你可能感兴趣的:(算法,java)