你需要解决的是服务器请求分配问题:给定k个环形排列的服务器和按时间递增到达的请求,按指定规则分配请求后,找出处理请求数量最多的所有服务器编号。这个问题的核心是高效管理服务器的空闲/忙碌状态,因为数据量(k和请求数)可达10^5,暴力解法会超时。
解题思路
要高效解决这个问题,需要两个核心数据结构:
- 有序集合(TreeSet):维护当前空闲的服务器编号,支持快速查找"i%k之后第一个空闲的服务器"(环形查找)。
- 最小堆(PriorityQueue):维护当前忙碌的服务器,存储(服务器结束时间,服务器编号),支持快速取出"最早结束的服务器",以便及时将其放回空闲集合。
具体步骤:
- 初始化空闲服务器集合(包含0~k-1)、忙碌服务器堆、请求计数数组。
- 遍历每个请求:
- 先清理忙碌堆:将所有结束时间≤当前请求到达时间的服务器放回空闲集合。
- 如果无空闲服务器,舍弃该请求。
- 否则,按规则找到目标服务器:
- 计算目标起点
target = i % k。 - 在空闲集合中找≥target的第一个服务器;若不存在,取空闲集合中最小的服务器(环形特性)。
- 计算目标起点
- 更新服务器状态:从空闲集合移除目标服务器,加入忙碌堆,计数数组+1。
- 遍历计数数组,找到最大值,收集所有等于最大值的服务器编号。
完整Java代码
java
import java.util.*;
public class BusiestServers {
public List<Integer> busiestServers(int k, int[] arrival, int[] load) {
// 1. 初始化数据结构
// 空闲服务器集合:有序,支持快速查找/删除
TreeSet<Integer> freeServers = new TreeSet<>();
for (int i = 0; i < k; i++) {
freeServers.add(i);
}
// 忙碌服务器堆:最小堆,元素为[结束时间, 服务器编号],按结束时间升序
PriorityQueue<int[]> busyServers = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
// 记录每个服务器处理的请求数
int[] count = new int[k];
// 2. 处理每个请求
int n = arrival.length;
for (int i = 0; i < n; i++) {
int arriveTime = arrival[i];
int processTime = load[i];
int endTime = arriveTime + processTime;
// 步骤1:清理忙碌堆,将已完成的服务器放回空闲集合
while (!busyServers.isEmpty() && busyServers.peek()[0] <= arriveTime) {
int freeServer = busyServers.poll()[1];
freeServers.add(freeServer);
}
// 步骤2:无空闲服务器,舍弃请求
if (freeServers.isEmpty()) {
continue;
}
// 步骤3:按规则找目标服务器
int target = i % k;
Integer selectedServer = freeServers.ceiling(target); // 找≥target的第一个空闲服务器
if (selectedServer == null) {
selectedServer = freeServers.first(); // 环形,取最小的空闲服务器
}
// 步骤4:更新服务器状态和计数
freeServers.remove(selectedServer);
busyServers.offer(new int[]{endTime, selectedServer});
count[selectedServer]++;
}
// 3. 找出处理请求最多的服务器
List<Integer> result = new ArrayList<>();
int maxCount = 0;
for (int c : count) {
maxCount = Math.max(maxCount, c);
}
for (int i = 0; i < k; i++) {
if (count[i] == maxCount) {
result.add(i);
}
}
return result;
}
// 测试用例
public static void main(String[] args) {
BusiestServers solution = new BusiestServers();
// 示例1
int k1 = 3;
int[] arrival1 = {1,2,3,4,5};
int[] load1 = {5,2,3,3,3};
System.out.println(solution.busiestServers(k1, arrival1, load1)); // 输出[1]
// 示例2
int k2 = 3;
int[] arrival2 = {1,2,3,4};
int[] load2 = {1,2,1,2};
System.out.println(solution.busiestServers(k2, arrival2, load2)); // 输出[0]
}
}
代码关键部分解释
- TreeSet的核心操作 :
ceiling(target):返回≥target的最小元素,对应"找i%k之后第一个空闲服务器"。first():返回集合中最小元素,对应环形查找时"从0开始找第一个空闲服务器"。
- PriorityQueue的核心作用 :
- 按结束时间升序排列,确保每次能优先取出最早完成的服务器,及时释放资源。
- 计数数组 :
- 简单直观地记录每个服务器处理的请求数,最后遍历即可找到最大值和对应服务器。
复杂度分析
- 时间复杂度:O(n log k)。每个请求的堆操作(poll/offer)和集合操作(add/remove/ceiling)都是O(log k),n个请求总复杂度为O(n log k),满足10^5数据量的要求。
- 空间复杂度:O(k)。空闲集合、忙碌堆、计数数组的空间均不超过k。
总结
- 核心数据结构:用
TreeSet高效管理空闲服务器(支持环形查找),用PriorityQueue高效管理忙碌服务器(按结束时间排序)。 - 关键逻辑:处理每个请求前先清理已完成的服务器,再按环形规则查找目标空闲服务器。
- 效率保障:所有核心操作都是O(log k)级别,整体算法能高效处理10^5规模的输入。