news 2026/11/4 13:05:06

LeetCode 928题解析:图论算法优化恶意软件传播控制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode 928题解析:图论算法优化恶意软件传播控制

1. 问题背景与核心挑战

今天我们来拆解LeetCode第928题"尽量减少恶意软件的传播II"。这是一道典型的图论与算法设计题目,考察我们对图遍历、感染模拟和优化策略的综合应用能力。题目给定一个无向图的邻接矩阵表示,以及一组初始被感染的节点,要求我们找到一个最优的初始感染节点进行"免疫",使得最终被感染的节点数量最少。

问题的实际背景可以类比计算机网络中病毒传播的场景:假设有若干台互相连接的计算机,其中部分机器已被病毒感染,病毒会通过网络连接不断传播。作为管理员,我们有一次机会可以彻底免疫(修复)其中一台初始感染的机器,目标是选择对整体传播影响最大的那个节点进行免疫,使得最终被感染的机器总数最少。

2. 问题形式化与输入输出分析

2.1 输入参数解析

题目给出的函数签名为:

public int minMalwareSpread(int[][] graph, int[] initial)
  • graph:一个n×n的对称邻接矩阵,表示无向图。graph[i][j] = 1表示节点i和j之间有边相连,0表示没有连接。graph[i][i]总是1(每个节点自连接)。

  • initial:一个包含初始感染节点索引的数组,这些节点最初被恶意软件感染。

2.2 输出要求

我们需要返回一个整数,表示应该从initial数组中选择哪个节点进行免疫(即从initial中移除)。如果有多个节点能同等程度地减少感染传播,则返回其中索引最小的那个。

2.3 示例解析

考虑以下示例:

graph = [[1,1,0],[1,1,0],[0,0,1]], initial = [0,1]

这个图有三个节点:

  • 节点0和1相连
  • 节点2独立
  • 初始感染节点是0和1

如果我们免疫节点0:

  • 剩余初始感染节点:1
  • 感染传播:从节点1感染节点0
  • 总感染节点:0,1

如果我们免疫节点1:

  • 剩余初始感染节点:0
  • 感染传播:从节点0感染节点1
  • 总感染节点:0,1

两种选择结果相同,按照规则返回较小的索引0。

3. 算法设计思路

3.1 暴力解法与优化方向

最直观的暴力解法是:

  1. 遍历initial中的每个节点u
  2. 模拟移除u后的感染传播过程
  3. 计算最终被感染的节点总数
  4. 选择使感染数最少的u,相同则取最小索引

这种方法的时间复杂度是O(k×n²),其中k是initial的长度,n是节点总数。当n较大时(题目限制n≤300),这种解法可能不够高效。

3.2 关键观察与优化策略

通过分析可以发现:

  1. 最终的感染源是initial中未被移除的所有节点
  2. 一个节点会被感染当且仅当它能被至少一个初始感染节点到达,且没有被免疫
  3. 我们需要计算每个节点u被移除后,能"拯救"多少个原本会被u单独感染的节点

基于这些观察,我们可以采用以下优化策略:

  • 对于每个不在initial中的节点v,记录哪些初始感染节点可以到达它
  • 如果一个v只能被一个初始感染节点u到达,那么移除u就能拯救v
  • 我们需要找到这样的u,它能拯救最多的v

3.3 具体算法步骤

  1. 对每个初始感染节点u∈initial:

    • 从u开始BFS,标记所有能到达的节点(排除其他initial节点)
    • 记录这些节点只能被u到达(即它们的"拯救者"是u)
  2. 统计每个u能拯救的节点数量

  3. 选择能拯救最多节点的u,相同则选索引最小的

这种方法的时间复杂度主要是O(k×n),比暴力解法更高效。

4. 代码实现与细节处理

4.1 Java实现代码

class Solution { public int minMalwareSpread(int[][] graph, int[] initial) { int n = graph.length; Arrays.sort(initial); // 记录每个节点能被哪些初始感染节点单独感染 int[] saveCount = new int[n]; Arrays.fill(saveCount, -1); for (int u : initial) { Queue<Integer> queue = new LinkedList<>(); boolean[] visited = new boolean[n]; queue.offer(u); visited[u] = true; while (!queue.isEmpty()) { int curr = queue.poll(); for (int v = 0; v < n; v++) { if (graph[curr][v] == 1 && !visited[v] && !isInitial(v, initial)) { visited[v] = true; if (saveCount[v] == -1) { saveCount[v] = u; } else if (saveCount[v] != -2) { // 被多个初始节点感染,标记为无效 saveCount[v] = -2; } queue.offer(v); } } } } // 统计每个初始节点能拯救的节点数 int[] rescueCount = new int[n]; for (int v = 0; v < n; v++) { if (saveCount[v] >= 0) { rescueCount[saveCount[v]]++; } } // 找到能拯救最多节点的初始节点 int maxRescue = -1; int result = initial[0]; for (int u : initial) { if (rescueCount[u] > maxRescue) { maxRescue = rescueCount[u]; result = u; } } return result; } private boolean isInitial(int v, int[] initial) { for (int u : initial) { if (u == v) return true; } return false; } }

4.2 关键实现细节

  1. 初始排序:首先对initial数组排序,确保在拯救数量相同时能直接返回最小的索引。

  2. 拯救计数数组:使用saveCount数组记录每个节点v能被哪个初始节点u单独拯救:

    • saveCount[v] = u:只能被u拯救
    • saveCount[v] = -2:能被多个初始节点感染,移除任何一个都无法单独拯救
    • saveCount[v] = -1:无法被任何初始节点感染
  3. BFS遍历:对每个初始节点u进行BFS,标记它能到达的所有非初始节点v,并更新saveCount[v]。

  4. 拯救统计:最后统计每个初始节点u能拯救的节点数量,选择拯救最多的u。

5. 复杂度分析与优化空间

5.1 时间复杂度

  • 排序initial数组:O(k log k),k是initial的长度
  • 主循环:对每个initial节点进行BFS,每次BFS是O(n),总共O(k×n)
  • 统计拯救数量:O(n)
  • 总时间复杂度:O(k log k + k×n)

由于k≤n≤300,这个复杂度是完全可行的。

5.2 空间复杂度

  • saveCount数组:O(n)
  • visited数组:O(n),每次BFS重用
  • 队列:最坏O(n)
  • 总空间复杂度:O(n)

5.3 可能的优化方向

  1. 提前终止:如果在BFS过程中发现某个节点已经被多个初始节点感染,可以提前终止对该路径的探索。

  2. 并行处理:对于大型图,可以考虑并行处理多个initial节点的BFS。

  3. 预处理连接组件:可以先找出所有连通组件,然后在组件内部处理感染传播。

6. 常见错误与调试技巧

6.1 常见错误类型

  1. 忽略初始节点的相互感染:在BFS时没有排除其他initial节点,导致错误计算拯救数量。

  2. 索引处理错误:特别是在处理initial数组和graph矩阵的索引时容易混淆。

  3. 边界条件处理不足:如initial长度为1,或graph全连接/全不连接等情况。

6.2 调试技巧

  1. 小规模测试用例:先用手算能验证的小例子测试,如3-4个节点的简单图。

  2. 打印中间状态:在BFS过程中打印visited数组和saveCount数组的变化。

  3. 可视化图结构:对于复杂案例,可以画出图结构辅助理解感染传播路径。

7. 实际应用与扩展思考

7.1 实际应用场景

这类算法在实际中有广泛应用:

  • 计算机网络中的病毒传播控制
  • 社交网络中谣言传播的抑制
  • 流行病学中的疫苗接种策略优化

7.2 问题变种与扩展

  1. 多节点免疫:如果允许免疫多个节点,问题将变得更加复杂,可能需要贪心算法或动态规划。

  2. 加权图:边可以带有传播概率或时间延迟,需要考虑更复杂的传播模型。

  3. 动态图:图结构随时间变化,需要设计在线算法实时调整免疫策略。

7.3 算法选择比较

与相关图论算法对比:

  • 与连通组件算法相比,需要更精细的感染来源追踪
  • 与最小顶点覆盖不同,我们关注的是传播影响而非覆盖所有边
  • 与影响力最大化问题有相似之处,但目标相反(最小化而非最大化传播)

8. 个人实现心得

在实现这道题目时,我最初尝试了暴力模拟的方法,虽然能通过小测试用例,但对于较大规模的图效率明显不足。通过分析问题本质,发现关键在于识别那些"只能被某一个初始节点感染"的节点,这种洞察大大简化了问题。

一个容易忽略的细节是初始节点之间的相互影响——即使我们移除了一个初始节点,其他初始节点仍然可能导致该节点被感染。这要求在BFS时需要特别处理其他初始节点。

在实际编码中,使用两个数组分别记录节点的拯救来源和每个初始节点的拯救数量,这种分离的设计使得逻辑更加清晰。另外,对initial数组进行预处理排序,巧妙地解决了"多个解时返回最小索引"的要求。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/4 13:04:18

大模型推理优化:突破内存墙与算力瓶颈的关键技术

1. 大模型推理的技术挑战全景大模型推理面临的核心技术挑战可以概括为"两墙一瓶颈"&#xff1a;内存墙、通信墙和算力瓶颈。这三个问题相互交织&#xff0c;构成了当前大模型落地的最大障碍。内存墙问题主要体现在模型参数规模与硬件内存容量之间的巨大鸿沟。以GPT-3…

作者头像 李华
网站建设 2026/11/4 13:01:44

七自由度车辆状态估计:CKF算法在智能驾驶中的应用

1. 项目背景与核心价值七自由度车辆状态估计是智能驾驶和车辆动力学控制领域的关键技术。传统车辆传感器只能提供有限的直接测量数据&#xff0c;而实际控制算法需要更全面的车辆状态信息。这个项目通过容积卡尔曼滤波&#xff08;CKF&#xff09;算法&#xff0c;实现了对车辆…

作者头像 李华
网站建设 2026/11/4 12:59:44

Recoil在OpenHarmony中的分布式状态管理实践

1. Recoil在OpenHarmony环境中的独特价值在React Native跨平台开发领域&#xff0c;状态管理一直是决定应用架构质量的关键因素。当我们将目光投向OpenHarmony这个新兴的国产操作系统平台时&#xff0c;传统的状态管理方案往往难以充分发挥鸿蒙系统的分布式特性。这正是Recoil这…

作者头像 李华
网站建设 2026/11/4 12:59:08

软考必备:设计模式核心解析与实战应用

1. 设计模式在软考中的核心地位 作为一名经历过多次软考并辅导过数十名考生的技术老兵&#xff0c;我深知设计模式在软件设计师考试中的分量。每次考试中&#xff0c;设计模式相关的题目至少会占据6-10分的分值&#xff0c;这往往就是能否通过的关键分数段。记得我第一次参加软…

作者头像 李华
网站建设 2026/11/4 12:57:11

识别欺骗性模型声明:用行为指纹与探测样本验证API真实性

1. 这个标题到底在说什么&#xff1a;从“模型声明”到“实际行为”的落差先把话说透&#xff1a;这个标题讨论的不是某个具体产品的好坏&#xff0c;而是一个在模型调用链条里越来越普遍的现象——你请求的是某个知名模型&#xff0c;实际返回结果的却可能是另一个模型&#x…

作者头像 李华
网站建设 2026/11/4 12:57:10

AI学术写作工具的核心功能与伦理实践

1. 学术写作工具的变革浪潮 去年帮导师审阅研究生论文时&#xff0c;一个现象让我震惊&#xff1a;超过60%的投稿在文献综述部分出现了惊人的相似结构。直到某天实验室的师弟悄悄给我展示了他使用的AI写作辅助工具&#xff0c;才解开了这个谜团。当前学术圈正在经历一场静默的技…

作者头像 李华