技术-算法-leetcode-1606. 找到处理最多请求的服务器(易懂版)

6 阅读4分钟

你需要解决的是服务器请求分配问题:给定k个环形排列的服务器和按时间递增到达的请求,按指定规则分配请求后,找出处理请求数量最多的所有服务器编号。这个问题的核心是高效管理服务器的空闲/忙碌状态,因为数据量(k和请求数)可达10^5,暴力解法会超时。 ### 解题思路 要高效解决这个问题,需要两个核心数据结构: 1. 有序集合(TreeSet):维护当前空闲的服务器编号,支持快速查找“i%k之后第一个空闲的服务器”(环形查找)。 2. 最小堆(PriorityQueue):维护当前忙碌的服务器,存储(服务器结束时间,服务器编号),支持快速取出“最早结束的服务器”,以便及时将其放回空闲集合。 具体步骤: 1. 初始化空闲服务器集合(包含0~k-1)、忙碌服务器堆、请求计数数组。 2. 遍历每个请求: - 先清理忙碌堆:将所有结束时间≤当前请求到达时间的服务器放回空闲集合。 - 如果无空闲服务器,舍弃该请求。 - 否则,按规则找到目标服务器: - 计算目标起点 target = i % k。 - 在空闲集合中找≥target的第一个服务器;若不存在,取空闲集合中最小的服务器(环形特性)。 - 更新服务器状态:从空闲集合移除目标服务器,加入忙碌堆,计数数组+1。 3. 遍历计数数组,找到最大值,收集所有等于最大值的服务器编号。 ### 完整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] } } ### 代码关键部分解释 1. TreeSet的核心操作: - ceiling(target):返回≥target的最小元素,对应“找i%k之后第一个空闲服务器”。 - first():返回集合中最小元素,对应环形查找时“从0开始找第一个空闲服务器”。 2. PriorityQueue的核心作用: - 按结束时间升序排列,确保每次能优先取出最早完成的服务器,及时释放资源。 3. 计数数组: - 简单直观地记录每个服务器处理的请求数,最后遍历即可找到最大值和对应服务器。 ### 复杂度分析 - 时间复杂度:O(n log k)。每个请求的堆操作(poll/offer)和集合操作(add/remove/ceiling)都是O(log k),n个请求总复杂度为O(n log k),满足10^5数据量的要求。 - 空间复杂度:O(k)。空闲集合、忙碌堆、计数数组的空间均不超过k。 ### 总结 1. 核心数据结构:用TreeSet高效管理空闲服务器(支持环形查找),用PriorityQueue高效管理忙碌服务器(按结束时间排序)。 2. 关键逻辑:处理每个请求前先清理已完成的服务器,再按环形规则查找目标空闲服务器。 3. 效率保障:所有核心操作都是O(log k)级别,整体算法能高效处理10^5规模的输入。