2025.04.09华为机考第一题


题目描述(补丁升级迭代)

某测试工具在升级时总是选择迭代次数最多的补丁版本进行升级。

给定若干补丁版本之间的迭代关系:

  • 每条关系表示“当前版本”是由“前序版本”修改得到的;

  • 一个版本最多只有一个前序版本(即单链分支);

  • NA 表示该版本没有前序版本(即为初始版本);

  • 不存在互为前序的情况(图为有向无环图 DAG);

请你返回最终可以升级的所有补丁版本,这些版本需要满足:

  • 从起点版本开始到该版本的路径长度(迭代次数)最长;

  • 若存在多个版本拥有相同的最大迭代次数,则按字典序升序输出


输入格式

第一行:一个整数 N(1 ≤ N ≤ 100000),表示补丁版本之间的迭代关系数量;
接下来 N 行:每行两个字符串,分别表示当前版本 和 它的前序版本,中间用空格隔开;
如果该版本没有前序版本,则第二个字符串为 NA。

输出格式

输出迭代次数最多的所有版本号(可能多个),用空格隔开,按字典序升序排序。

示例输入

6
A NA
B A
C A
D B
E C
F C

示例输出

D E F

解题思路详解

这个问题可以转化为 图的最长路径终点查找

  1. 把所有补丁版本看成图中的节点;

  2. 每条版本迭代关系是一个有向边(从前序版本指向当前版本);

  3. 从入度为 0 的版本(前序为 NA)出发,使用 拓扑排序 + 动态规划 计算从起点到每个版本的最大迭代次数;

  4. 最终找出最大迭代次数的所有版本;

  5. 按字典序排序并输出。


Java 实现 + 注释

import java.util.*;

public class PatchUpgrade {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = Integer.parseInt(scanner.nextLine()); // 读取关系条数

        // 图的结构:当前版本 <- 前序版本
        Map> graph = new HashMap<>();
        Map inDegree = new HashMap<>();  // 记录每个节点的入度
        Map dp = new HashMap<>();        // dp[version]:当前版本的最大迭代次数
        Set allVersions = new HashSet<>();        // 所有出现过的版本

        for (int i = 0; i < n; i++) {
            String[] parts = scanner.nextLine().split(" ");
            String cur = parts[0];     // 当前版本
            String pre = parts[1];     // 前序版本

            allVersions.add(cur);

            // 如果不是NA,表示有前序版本
            if (!pre.equals("NA")) {
                // 建立前序版本 -> 当前版本 的映射
                graph.computeIfAbsent(pre, k -> new ArrayList<>()).add(cur);

                // 当前版本入度+1
                inDegree.put(cur, inDegree.getOrDefault(cur, 0) + 1);
            }

            // 保证图中所有节点都初始化(避免空指针)
            inDegree.putIfAbsent(cur, inDegree.getOrDefault(cur, 0));
            dp.putIfAbsent(cur, 0);
            dp.putIfAbsent(pre, 0); // NA 也初始化
        }

        // 使用队列进行拓扑排序
        Queue queue = new LinkedList<>();
        for (String version : allVersions) {
            if (inDegree.getOrDefault(version, 0) == 0) {
                queue.offer(version);
                dp.put(version, 1); // 起始版本迭代次数为1
            }
        }

        int maxIter = 1; // 记录最大迭代次数

        // 拓扑排序 + 动态规划更新最长路径
        while (!queue.isEmpty()) {
            String cur = queue.poll();
            int curIter = dp.get(cur); // 当前版本的最大迭代次数

            // 遍历所有后继版本
            for (String next : graph.getOrDefault(cur, new ArrayList<>())) {
                // 动态规划:更新后继版本的迭代次数
                dp.put(next, Math.max(dp.get(next), curIter + 1));

                // 更新最大路径
                maxIter = Math.max(maxIter, dp.get(next));

                // 入度减1,如果入度为0加入队列
                inDegree.put(next, inDegree.get(next) - 1);
                if (inDegree.get(next) == 0) {
                    queue.offer(next);
                }
            }
        }

        // 收集所有最大迭代次数的版本
        List result = new ArrayList<>();
        for (String version : dp.keySet()) {
            if (dp.get(version) == maxIter) {
                result.add(version);
            }
        }

        // 按字典序排序
        Collections.sort(result);

        // 输出
        System.out.println(String.join(" ", result));
    }
}

总结一句话

  • 本题的核心是:拓扑排序找最长路径的末端节点

  • 与一般最长路径不同,我们只记录每个节点到源节点的最大深度(通过 DP 实现);

  • 最后找出所有等于最大深度的节点,按字典序输出。

你可能感兴趣的:(算法,华为机考,java)