在工作中学算法——专线配置

小明是一名网络工程师,他负责管理公司数据中心里的一台或多台核心交换机。

  • 初始配置 (configs): 当他开始一天的工作时,这台交换机上已经存在一些配置好的"专线"(Dedicated Lines)。这里的专线可以理解为为某个重要客户(比如一个银行)或某个关键业务(比如在线支付)预留的、点对点的专用网络通道。例如 [2, 3] 就表示交换机的 2 号端口和 3 号端口之间已经建立了一条专线,这两个端口目前正被这项业务占用。这个初始状态就是 configs 数组。

  • 配置请求 (batchReqs): 在一天的工作中,他会收到来自不同部门的各种工作单(Work Orders),这些就是 batchReqs

    • ["a", 0, 1] (Add): "市场部新上了一个在线活动网站,需要一条新的专线连接服务器,请占用 0 号和 1 号端口。" 作为工程师,小明首先要检查 0 号和 1 号端口是不是空闲的,如果是,就在配置计划中加上这条新线。如果任何一个端口已经被占用了,这个请求就无法完成。
    • ["d", 4, 5] (Delete): "财务部的那个旧服务器要下线了,请拆除连接 4 号和 5 号端口的专线。" 小明需要确认这条专线确实存在,然后才能在配置计划中将它移除,并释放这两个端口。
  • 批量提交与命令简化: 直接登录到昂贵且关键的核心设备上频繁地敲命令,既耗时又容易出错。因此,现代网管软件允许小明先在软件界面上完成所有计划的添加和删除操作。 所有操作都确认无误后,小明点击"提交"按钮。此时,软件并不会把小明刚才的每一步操作都原样发给设备。它会智能地对比设备的初始状态 (configs) 和你操作完成后的最终目标状态 ,然后计算出一个最短的、最有效的命令列表来完成这个状态变更。

    • 例如,小明先 add [0,1],后来又 delete [0,1]。对于最终结果来说,这条线没有变化,所以软件就不应该向设备发送任何关于 [0,1] 的命令。

现在我们将问题抽象一下。

给定:

  1. 一个初始集合 (Initial Set) S_initial,其元素是无序数对 {a, b}
  2. 一个操作序列 (Operation Sequence) Ops,其中每个操作是 (type, {x, y})type 为"添加"或"删除"。

任务:

  1. 模拟状态演变 (Simulate State Evolution):

    • 从一个当前集合 (Current Set) S_current 开始,初始时 S_current = S_initial

    • 维护一个已使用元素集合 (Used Elements Set) U,初始时包含 S_initial 中所有数对里的所有元素。

    • 依次处理 Ops 中的每一个操作 (type, {x, y})

      • 如果 type添加 :当且仅当元素 xy 都未 出现在 U 中时,将 {x, y} 加入 S_current,并将 xy 加入 U
      • 如果 type删除 :当且仅当 {x, y} 存在于 S_current 中时,从 S_current 中移除 {x, y},并从 U 中移除 xy
    • 所有操作处理完毕后,得到一个最终集合 (Final Set) S_final (即演变后的 S_current)。

  2. 计算集合差异 (Calculate Set Difference):

    • 我们的目标是生成一组最少的命令,将 S_initial 变换为 S_final。这组命令就是这两个集合的对称差 (Symmetric Difference)
    • 需要删除的元素集合: Deletions = S_initial - S_final (即,在初始集合中存在,但在最终集合中不存在的元素)。
    • 需要添加的元素集合: Additions = S_final - S_initial (即,在最终集合中存在,但在初始集合中不存在的元素)。
  3. 格式化输出 (Format Output):

    • Deletions 集合中的每个数对 {a, b} 格式化为 ["d", a, b]
    • Additions 集合中的每个数对 {a, b} 格式化为 ["a", a, b]
    • 对两组命令分别按数对的第一个元素升序排序。
    • 最终返回一个列表,其中包含所有排好序的删除命令,后面跟着所有排好序的添加命令。
java 复制代码
import java.util.*;
import java.util.stream.Collectors;

class SpecializedLineConfigurator {

    /**
     * 内部辅助类,用于规范化表示一个专线连接。
     * 通过在构造函数中排序,确保 (id1, id2) 和 (id2, id1) 被视为等价。
     */
    private static class ConnectionPair {
        final int id1;
        final int id2;

        ConnectionPair(int id1, int id2) {
            // 始终将较小的 ID 放在前面,作为规范表示
            this.id1 = Math.min(id1, id2);
            this.id2 = Math.max(id1, id2);
        }

        // 重写 equals 和 hashCode 方法,是让 HashSet 能正确工作的关键
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            ConnectionPair that = (ConnectionPair) o;
            return id1 == that.id1 && id2 == that.id2;
        }

        @Override
        public int hashCode() {
            return Objects.hash(id1, id2);
        }
    }

    /**
     * 主逻辑方法,处理所有操作并返回简化后的命令列表。
     * @param configs 初始配置
     * @param batchReqs 批量配置请求
     * @return 经过简化的、需要下发到设备的命令列表
     */
    public List<List<Object>> getSimplifiedCommands(int[][] configs, Object[][] batchReqs) {
        // --- 1. 初始化 ---
        Set<ConnectionPair> initialState = new HashSet<>();
        Set<Integer> occupiedPorts = new HashSet<>();

        // 从 configs 初始化 initialState 和 occupiedPorts
        for (int[] config : configs) {
            ConnectionPair pair = new ConnectionPair(config[0], config[1]);
            initialState.add(pair);
            occupiedPorts.add(config[0]);
            occupiedPorts.add(config[1]);
        }
        // currentState 从 initialState 的一个副本开始
        Set<ConnectionPair> currentState = new HashSet<>(initialState);

        // --- 2. 模拟批量请求,更新网管侧的当前配置 ---
        for (Object[] req : batchReqs) {
            String opType = (String) req[0];
            int id1 = (Integer) req[1];
            int id2 = (Integer) req[2];
            ConnectionPair pair = new ConnectionPair(id1, id2);

            if ("a".equals(opType)) {
                // 如果两个端口都未被占用,则添加成功
                if (!occupiedPorts.contains(id1) && !occupiedPorts.contains(id2)) {
                    currentState.add(pair);
                    occupiedPorts.add(id1);
                    occupiedPorts.add(id2);
                }
            } else if ("d".equals(opType)) {
                // 如果专线存在,则删除成功
                if (currentState.contains(pair)) {
                    currentState.remove(pair);
                    occupiedPorts.remove(id1);
                    occupiedPorts.remove(id2);
                }
            }
        }
        // --- 3. 计算配置差异 ---
        List<List<Object>> deleteCommands = new ArrayList<>();
        // 初始状态有,但当前状态没有 -> 需要删除
        for (ConnectionPair pair : initialState) {
            if (!currentState.contains(pair)) {
                deleteCommands.add(Arrays.asList("d", pair.id1, pair.id2));
            }
        }
        List<List<Object>> addCommands = new ArrayList<>();
        // 当前状态有,但初始状态没有 -> 需要添加
        for (ConnectionPair pair : currentState) {
            if (!initialState.contains(pair)) {
                addCommands.add(Arrays.asList("a", pair.id1, pair.id2));
            }
        }

        // --- 4. 排序并合并命令列表 ---
        // 按 id1 升序对删除命令排序
        deleteCommands.sort(Comparator.comparingInt(cmd -> (int) cmd.get(1)));
        // 按 id1 升序对添加命令排序
        addCommands.sort(Comparator.comparingInt(cmd -> (int) cmd.get(1)));
        // 先添加所有删除命令,再添加所有添加命令
        List<List<Object>> finalCommands = new ArrayList<>();
        finalCommands.addAll(deleteCommands);
        finalCommands.addAll(addCommands);
        return finalCommands;
    }
}


public class Main {
    public static void main(String[] args) {
        SpecializedLineConfigurator solver = new SpecializedLineConfigurator();
        // 样例 1
        System.out.println("--- 样例 1 ---");
        int[][] configs1 = {{2, 3}, {4, 5}};
        Object[][] batchReqs1 = {{"a", 0, 1}, {"a", 5, 6}, {"d", 0, 1}, {"d", 1, 2}, {"a", 0, 6}, {"d", 4, 5}, {"a", 1, 4}};
        List<List<Object>> result1 = solver.getSimplifiedCommands(configs1, batchReqs1);
        // 使用 stream 和 map 来美化输出,使其看起来像 [[d, 4, 5], [a, 0, 6], [a, 1, 4]]
        System.out.println(result1.stream().map(Arrays::toString).collect(Collectors.toList()));

        System.out.println();
        // 样例 2
        System.out.println("--- 样例 2 ---");
        int[][] configs2 = {};
        Object[][] batchReqs2 = {{"a", 10, 20}, {"a", 50, 60}, {"d", 50, 60}, {"d", 10, 20}};
        List<List<Object>> result2 = solver.getSimplifiedCommands(configs2, batchReqs2);
        System.out.println(result2.stream().map(Arrays::toString).collect(Collectors.toList()));
    }
}
相关推荐
Jerry9 小时前
LeetCode 28. 找出字符串中第一个匹配项的下标
算法
Jerry11 小时前
LeetCode 459. 重复的子字符串
算法
闲猫13 小时前
Spring AI 对接Deepseek ChatModel 聊天对话
java·前端·spring
海石13 小时前
1500分的题目,确实有实力,不过还是我略胜一筹
算法·leetcode
海石13 小时前
【记忆化搜索】条条大路通AC,走好适合你的那一条,走到后再考虑走得快
算法·leetcode
自信的未来15 小时前
JSON 工具|Web Worker 工程化打包 + 语法自动修复 + 多语言代码生成实战
java·前端·json
Brookty15 小时前
【JavaEE】线程安全(一).4:写块串行保安全、CAS
java·开发语言·java-ee·多线程·线程安全
Jerry15 小时前
LeetCode 151. 反转字符串中的单词
算法
怕孤单的草丛16 小时前
缓存管理面临的主要问题
java·数据库·缓存