话单发送模拟

51 阅读7分钟

话单发送

问题背景

某核心网设备需要向计费网关发送一批话单(通话记录)。每个话单都具有 长度(length)优先级(priority) 两个属性。设备在发送话单时,必须遵循特定的规则,并且受到自身**承载规格(capacity)**的限制。

发送规则

  1. 优先级绝对优先:

    • 话单必须严格按照优先级从高到低的顺序发送。
    • 优先级的值越小,代表其优先级越高(例如,优先级 1 高于优先级 2)。
    • 只有当一个优先级的所有话单都成功发送(或尝试发送)完毕后,才能开始处理下一个优先级的活单。
  2. 容量限制:

    • 设备有一个固定的承载规格 cap,代表其能发送的话单总容量。
    • 所有成功发送的话单,其长度之和不能超过 cap
  3. 同优先级发送顺序:

    • 对于同一优先级的多个话单,为了发送尽可能多的话单,应该优先发送长度较小的话单。
  4. 阻塞规则:

    • 如果在处理某一个优先级时,因为容量不足导致该优先级的部分话单无法发送,那么所有更低优先级的话单将全部被阻塞,无法发送

任务要求

给定设备的承载规格 cap,以及一批话单的长度列表 bills 和对应的优先级列表 priorities,请计算在遵循上述规则的前提下,最多可以成功发送多少个话单。


输入格式

  • cap: 第一个参数,一个整数,表示设备的总承载规格。

    • 1 ≤ cap ≤ 10000
  • bills: 第二个参数,一个整数数组,表示每个话单的长度。

    • 0 ≤ bills[i] ≤ 1000
    • 0 ≤ 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. 数据整理: 首先,我们将话单按优先级分组,并在每个组内按长度升序排列。

    • 优先级 1: [30, 50]
    • 优先级 2: [20, 50]
    • 优先级 3: [10]
  2. 模拟发送过程:

    • 处理优先级 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);
    }
}