leetcode
leetcode 2701 ~ 2750
使用服务器处理任务

使用服务器处理任务

难度:

标签:

题目描述

You are given two 0-indexed integer arrays servers and tasks of lengths n​​​​​​ and m​​​​​​ respectively. servers[i] is the weight of the i​​​​​​th​​​​ server, and tasks[j] is the time needed to process the j​​​​​​th​​​​ task in seconds.

Tasks are assigned to the servers using a task queue. Initially, all servers are free, and the queue is empty.

At second j, the jth task is inserted into the queue (starting with the 0th task being inserted at second 0). As long as there are free servers and the queue is not empty, the task in the front of the queue will be assigned to a free server with the smallest weight, and in case of a tie, it is assigned to a free server with the smallest index.

If there are no free servers and the queue is not empty, we wait until a server becomes free and immediately assign the next task. If multiple servers become free at the same time, then multiple tasks from the queue will be assigned in order of insertion following the weight and index priorities above.

A server that is assigned task j at second t will be free again at second t + tasks[j].

Build an array ans​​​​ of length m, where ans[j] is the index of the server the j​​​​​​th task will be assigned to.

Return the array ans​​​​.

 

Example 1:

Input: servers = [3,3,2], tasks = [1,2,3,2,1,2]
Output: [2,2,0,2,1,2]
Explanation: Events in chronological order go as follows:
- At second 0, task 0 is added and processed using server 2 until second 1.
- At second 1, server 2 becomes free. Task 1 is added and processed using server 2 until second 3.
- At second 2, task 2 is added and processed using server 0 until second 5.
- At second 3, server 2 becomes free. Task 3 is added and processed using server 2 until second 5.
- At second 4, task 4 is added and processed using server 1 until second 5.
- At second 5, all servers become free. Task 5 is added and processed using server 2 until second 7.

Example 2:

Input: servers = [5,1,4,3,2], tasks = [2,1,2,4,5,2,1]
Output: [1,4,1,4,1,3,2]
Explanation: Events in chronological order go as follows: 
- At second 0, task 0 is added and processed using server 1 until second 2.
- At second 1, task 1 is added and processed using server 4 until second 2.
- At second 2, servers 1 and 4 become free. Task 2 is added and processed using server 1 until second 4. 
- At second 3, task 3 is added and processed using server 4 until second 7.
- At second 4, server 1 becomes free. Task 4 is added and processed using server 1 until second 9. 
- At second 5, task 5 is added and processed using server 3 until second 7.
- At second 6, task 6 is added and processed using server 2 until second 7.

 

Constraints:

  • servers.length == n
  • tasks.length == m
  • 1 <= n, m <= 2 * 105
  • 1 <= servers[i], tasks[j] <= 2 * 105

代码结果

运行时间: 577 ms, 内存: 58.9 MB


/*
 * Problem: Given two arrays, servers and tasks, representing server weights and task durations,
 * allocate tasks to servers based on the given rules and return the allocation order.
 * 
 * Approach using Java Streams:
 * 1. Use two priority queues (one for free servers and one for busy servers).
 * 2. Free servers are ordered by weight and index, busy servers by finish time.
 * 3. For each task, allocate it to the first available free server or wait if none are free.
 */

import java.util.*;
import java.util.stream.IntStream;

class Solution {
    public int[] assignTasks(int[] servers, int[] tasks) {
        int n = servers.length;
        int m = tasks.length;
        int[] ans = new int[m];

        PriorityQueue<int[]> freeServers = new PriorityQueue<>((a, b) -> a[0] == b[0] ? a[1] - b[1] : a[0] - b[0]);
        PriorityQueue<int[]> busyServers = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));

        IntStream.range(0, n).forEach(i -> freeServers.offer(new int[]{servers[i], i}));

        IntStream.range(0, m).forEach(j -> {
            while (!busyServers.isEmpty() && busyServers.peek()[0] <= j) {
                int[] server = busyServers.poll();
                freeServers.offer(new int[]{server[1], server[2]});
            }

            if (freeServers.isEmpty()) {
                int[] server = busyServers.poll();
                ans[j] = server[2];
                busyServers.offer(new int[]{server[0] + tasks[j], server[1], server[2]});
            } else {
                int[] server = freeServers.poll();
                ans[j] = server[1];
                busyServers.offer(new int[]{j + tasks[j], server[0], server[1]});
            }
        });

        return ans;
    }
}

解释

方法:

该题解使用了两个优先队列(堆)来管理服务器的状态:一个是空闲服务器堆(idle),另一个是忙碌服务器堆(busy)。空闲服务器堆按服务器权重和索引排序,确保总是从权重最小且索引最小的服务器分配任务。忙碌服务器堆则按服务器空闲的时间、权重和索引排序,确保最早空闲的服务器可以重新被分配。对于每一个任务,首先检查是否有服务器已经完成任务并因此变为空闲,如果有,则将其从忙碌堆移到空闲堆。然后,如果有空闲的服务器,立即分配当前任务;如果没有,则从忙碌堆中取出最早空闲的服务器,并将其继续排入忙碌堆,更新其空闲时间。最后记录分配的服务器索引。

时间复杂度:

O(m log n)

空间复杂度:

O(n)

代码细节讲解

🦆
在处理任务时,如何确保同时有多项任务等待和多台服务器同时空闲时,能够正确地按照题目要求的权重最小和下标最小的规则进行分配?
在题解中,我们使用了一个最小堆(优先队列)来处理空闲服务器。堆中的元素是一个元组,其中包括服务器的权重和索引,且元组的排序首先是按权重升序,其次是按索引升序。这样的排序规则确保了每次从空闲堆中取出的服务器都是当前权重最小的,如果权重相同则取索引最小的。因此,当多项任务等待和多台服务器同时空闲时,堆的这种特性保证了能够按照题目要求的权重最小和下标最小的规则进行服务器的分配。
🦆
为什么选择使用两个优先队列来管理服务器的状态,而不是使用其他数据结构如列表或单一队列?
使用两个优先队列(堆)来管理服务器状态的主要理由是优先队列能够有效地支持动态数据的插入和删除操作,并且能够保证每次取出的都是最优(最小或最大)的元素。在这个场景中,一个优先队列用于管理空闲服务器,另一个用于管理忙碌服务器。空闲服务器堆能够确保每次都能快速地按权重和索引的优先级分配服务器,而忙碌服务器堆则按服务器空闲的时间排序,这样可以确保最早空闲的服务器能够优先重新被分配。如果使用列表或单一队列,我们不仅需要手动维护排序,每次服务器状态更新时还可能需要进行复杂的插入或删除操作,这会使得时间复杂度上升,效率降低。因此,两个优先队列的使用是为了最大化效率和符合题目分配逻辑的需要。

相关问题