话单发送
问题背景
某核心网设备需要向计费网关发送一批话单(通话记录)。每个话单都具有 长度(length) 和 优先级(priority) 两个属性。设备在发送话单时,必须遵循特定的规则,并且受到自身**承载规格(capacity)**的限制。
发送规则
-
优先级绝对优先:
- 话单必须严格按照优先级从高到低的顺序发送。
- 优先级的值越小,代表其优先级越高(例如,优先级
1高于优先级2)。 - 只有当一个优先级的所有话单都成功发送(或尝试发送)完毕后,才能开始处理下一个优先级的活单。
-
容量限制:
- 设备有一个固定的承载规格
cap,代表其能发送的话单总容量。 - 所有成功发送的话单,其长度之和不能超过
cap。
- 设备有一个固定的承载规格
-
同优先级发送顺序:
- 对于同一优先级的多个话单,为了发送尽可能多的话单,应该优先发送长度较小的话单。
-
阻塞规则:
- 如果在处理某一个优先级时,因为容量不足导致该优先级的部分话单无法发送,那么所有更低优先级的话单将全部被阻塞,无法发送。
任务要求
给定设备的承载规格 cap,以及一批话单的长度列表 bills 和对应的优先级列表 priorities,请计算在遵循上述规则的前提下,最多可以成功发送多少个话单。
输入格式
-
cap: 第一个参数,一个整数,表示设备的总承载规格。1 ≤ cap ≤ 10000
-
bills: 第二个参数,一个整数数组,表示每个话单的长度。0 ≤ bills[i] ≤ 10000 ≤ bills.length ≤ 100
-
priorities: 第三个参数,一个整数数组,表示每个话单的优先级。0 ≤ priorities[i] ≤ 30
-
保证:
bills.length == priorities.length,即bills[i]和priorities[i]描述的是同一个话单。
输出格式
- 一个整数,代表最多能发送的话单总数。
样例说明
样例输入
cap = 110
bills = [50, 20, 30, 10, 50]
priorities = [2, 2, 1, 3, 1]
样例输出
3
解释
-
数据整理: 首先,我们将话单按优先级分组,并在每个组内按长度升序排列。
- 优先级 1:
[30, 50] - 优先级 2:
[20, 50] - 优先级 3:
[10]
- 优先级 1:
-
模拟发送过程:
-
处理优先级 1:
-
初始容量
cap = 110。 -
发送长度为
30的话单。发送成功。- 剩余容量:
110 - 30 = 80。 - 已发送话单数:
1。
- 剩余容量:
-
发送长度为
50的话单。发送成功。- 剩余容量:
80 - 50 = 30。 - 已发送话单数:
2。
- 剩余容量:
-
优先级 1 的所有话单都已发送完毕。继续处理下一优先级。
-
-
处理优先级 2:
-
当前容量
cap = 30。 -
尝试发送该组内长度最小的话单,即长度为
20的话单。发送成功。- 剩余容量:
30 - 20 = 10。 - 已发送话单数:
3。
- 剩余容量:
-
尝试发送该组内下一个话单,长度为
50。- 当前剩余容量
10小于话单长度50,发送失败。
- 当前剩余容量
-
由于优先级 2 的话单未能全部发送,根据阻塞规则,处理流程终止。
-
-
处理优先级 3:
- 因为优先级 2 的处理被阻塞,所以优先级 3 的所有话单都无法发送。
-
-
最终结果:
- 总共成功发送了 3 个话单。因此,输出为
3。
- 总共成功发送了 3 个话单。因此,输出为
import java.util.*;
/**
* 算法思路:
* 1. **数据分组与排序**:
* - 首先,需要将话单按照优先级进行分组。使用 `TreeMap` 是一个很好的选择,因为它的键(优先级)是自动排序的,这确保了我们能按从高到低(数值从小到大)的顺序处理优先级。
* - Map 的结构为 `Map<Integer, List<Integer>>`,其中 Key 是优先级,Value 是该优先级下所有话单长度的列表。
*
* 2. **贪心策略**:
* - 遍历排序后的优先级。
* - 对于每个优先级内部的话单列表,我们同样需要应用贪心策略。为了在有限的容量内发送尽可能多的**数量**,我们应该优先发送**长度最短**的话单。因此,需要对每个优先级的话单列表按长度进行升序排序。
*
* 3. **模拟发送过程**:
* - 按优先级的顺序,逐级处理。
* - **情况一:容量充足**。计算当前优先级下所有话单的总长度。如果剩余容量 `currentCap` 足够容纳所有这些话单,则将它们全部“发送”(增加已发送计数,并从 `currentCap` 中减去总长度),然后继续处理下一个优先级。
* - **情况二:容量不足**。如果剩余容量不足以发送当前优先级的所有话单,则根据规则,我们不能再考虑更低的优先级了。此时,我们就在当前优先级内,按照长度从小到大的顺序,尽可能多地发送话单,直到 `currentCap` 耗尽。然后,整个过程结束。
*/
public class Solution {
/**
* 计算在给定承载规格下,按规则最多可以发送的话单数量。
*
* @param cap 设备的承载规格(总容量)。
* @param bills 每个话单的长度数组。
* @param priorities 每个话单对应的优先级数组。
* @return 最多能发送的话单个数。
*/
public int sendBills(int cap, int[] bills, int[] priorities) {
// --- 1. 数据预处理:按优先级对账单进行分组和排序 ---
// 使用 TreeMap 可以自动按 Key (优先级) 升序排序。
// Key: 优先级 (Integer), Value: 对应优先级的所有话单长度列表 (List<Integer>)
Map<Integer, List<Integer>> billsByPriority = new TreeMap<>();
// 遍历输入,将话单按优先级分组
for (int i = 0; i < bills.length; i++) {
int priority = priorities[i];
int billLength = bills[i];
// computeIfAbsent: 如果 map 中不存在该 priority,则创建一个新的 ArrayList,
// 否则返回现有的 List。然后将当前话单长度加入该列表。
billsByPriority.computeIfAbsent(priority, k -> new ArrayList<>()).add(billLength);
}
// --- 2. 模拟发送过程 ---
int sentCount = 0; // 记录已成功发送的话单总数
long currentCap = cap; // 当前剩余的承载容量,使用 long 防止计算中发生问题
// 遍历按优先级排序后的 Map。TreeMap 的 entrySet() 迭代器会按键的升序返回条目。
for (Map.Entry<Integer, List<Integer>> entry : billsByPriority.entrySet()) {
// int priority = entry.getKey(); // 当前处理的优先级
List<Integer> currentPriorityBills = entry.getValue(); // 当前优先级的所有话单
// a. 对当前优先级的话单按长度升序排序。
// 这是贪心策略:为了在有限容量内发送尽可能多的话单,总是优先发送最短的。
Collections.sort(currentPriorityBills);
// b. 尝试一次性发送当前优先级的所有话单
long totalLengthThisPriority = 0;
for (int bill : currentPriorityBills) {
totalLengthThisPriority += bill;
}
// c. 判断是否能全部发送
if (currentCap >= totalLengthThisPriority) {
// 如果剩余容量足够,则全部发送
sentCount += currentPriorityBills.size();
currentCap -= totalLengthThisPriority;
// 继续处理下一个(更低的)优先级
} else {
// 如果剩余容量不足以发送当前优先级的所有话单,
// 则按长度从小到大,尽可能多地发送,然后必须停止。
for (int billLength : currentPriorityBills) {
if (currentCap >= billLength) {
// 容量足够发送这个短话单
currentCap -= billLength;
sentCount++;
} else {
// 容量不足以发送这个话单,更长的也肯定无法发送
break; // 停止尝试当前优先级的话单
}
}
// 根据规则,一旦某个优先级的话单未被完全发送,就不能再处理更低的优先级了。
// 因此,直接跳出主循环。
break;
}
}
// --- 3. 返回结果 ---
return sentCount;
}
}
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取承载规格 cap
int cap = Integer.parseInt(scanner.nextLine().trim());
// 读取话单长度 bills 数组
String[] billsStr = scanner.nextLine().trim().split("\s+");
int[] bills = new int[billsStr.length];
if (billsStr.length == 1 && billsStr[0].isEmpty()) {
bills = new int[0];
} else {
for (int i = 0; i < billsStr.length; i++) {
bills[i] = Integer.parseInt(billsStr[i]);
}
}
// 读取优先级 priorities 数组
String[] prioritiesStr = scanner.nextLine().trim().split("\s+");
int[] priorities = new int[prioritiesStr.length];
if (prioritiesStr.length == 1 && prioritiesStr[0].isEmpty()) {
priorities = new int[0];
} else {
for (int i = 0; i < prioritiesStr.length; i++) {
priorities[i] = Integer.parseInt(prioritiesStr[i]);
}
}
scanner.close();
// 创建 Solution 类的实例并调用方法
Solution solution = new Solution();
int result = solution.sendBills(cap, bills, priorities);
// 输出最终结果
System.out.println(result);
}
}