【OD机试题解法笔记】电脑病毒感染

题目


一个局域网内有很多台电脑,分别标注为 0 ~ N-1 的数字。相连接的电脑距离不一样,所以感染时间不一样,感染时间用 t 表示。
其中网络内一台电脑被病毒感染,求其感染网络内所有的电脑最少需要多长时间。如果最后有电脑不会感染,则返回-1。
给定一个数组 times 表示一台电脑把相邻电脑感染所用的时间。
如图:path[i] = {i, j, t} 表示:电脑 i->j,电脑 i 上的病毒感染 j,需要时间 t。

输入描述
第一行输入一个整数N ,表示局域网内电脑个数 N ,1 ≤ N ≤ 200 ;
第二行输入一个整数M ,表示有 M 条网络连接;
接下来M行 ,每行输入为 i , j , t 。表示电脑 i 感染电脑j 需要时间 t 。(1 ≤ i , j ≤ N)
最后一行为病毒所在的电脑编号。

输出描述
输出最少需要多少时间才能感染全部电脑,如果不存在输出 -1

用例

输入输出说明
4
3
2 1 1
2 3 1
3 4 1
2
2第一个参数:局域网内电脑个数N,1 ≤ N ≤ 200;
第二个参数:总共多少条网络连接
第三个 2 1 1 表示2->1时间为1
第六行:表示病毒最开始所在电脑号2

思考

求病毒在局域网内传播感染多台电脑的最短时间。电脑看成无向图中的节点,电脑 i 感染 电脑 j 所需时间 t 看成边的权重,求单源最短路径问题,即从病毒源开始访问所有节点的最短路径集合中求最大值就是题目答案。权重时间是大于 0 的,可以用 Dijkstra 算法求解。

算法过程

Dijkstra 算法过程描述

Dijkstra 算法是一种用于计算带权有向图中单源最短路径的经典算法,适用于边权非负的情况。以下是针对 "局域网病毒感染时间" 问题的算法详细步骤:

算法核心思想

从初始感染源出发,逐步扩展到所有可达节点,每次选择当前已知距离最小的节点进行处理,确保每个节点的最短路径被最早确定。

具体步骤

1. 输入处理与图构建
  • 读取输入参数:获取电脑数量 N、网络连接数 M、每条连接的感染时间 t,以及初始感染源电脑编号。

  • 构建邻接表:将输入转换为图的邻接表表示,其中每个节点 i 对应一个列表,列表中存储所有从 i 出发的边 (j, t),表示感染从 i 到 j 需要时间 t

2. 初始化距离数组
  • 创建一个数组 dist,其中 dist[j] 表示从初始感染源到节点 j 的最短感染时间。

  • 初始时,dist[初始节点] = 0,其余节点的 dist 值设为无穷大(表示尚未可达)。

3. 优先队列初始化
  • 使用优先队列(最小堆)存储待处理的节点,初始时队列中仅包含初始感染源节点,优先级为其距离值(0)。

4. 循环处理优先队列
  • 取出当前最小距离节点:从优先队列中取出距离值最小的节点 u

  • 检查是否已处理:若当前取出的距离值大于 dist[u],说明该节点已被处理过,跳过。

  • 遍历邻居节点:对于节点 u 的每个邻居 j,检查是否可以通过 u 缩短到 j 的距离:

    • 若 dist[j] > dist[u] + t(u,j),则更新 dist[j] = dist[u] + t(u,j),并将 j 加入优先队列。

5. 结果判断
  • 遍历所有节点的 dist 值:

    • 若存在节点的 dist 值仍为无穷大,说明无法感染所有电脑,返回 -1

    • 否则,返回 dist 数组中的最大值,即感染所有电脑所需的最少时间。

参考代码一(朴素版本)

function solution() {
    const n = parseInt(readline());
    const m = parseInt(readline());

    // 邻接矩阵 or 邻接表?这里用邻接表更省空间
    const adj = Array.from({ length: n }, () => []);
    for (let i = 0; i < m; i++) {
        const [u, v, t] = readline().split(' ').map(Number);
        adj[u - 1].push([v - 1, t]); // 转为 0-based
    }

    const start = parseInt(readline()) - 1;

    // 初始化
    const dist = Array(n).fill(Infinity);
    const visited = Array(n).fill(false);
    dist[start] = 0;

    // 主循环:执行 n 次
    for (let iter = 0; iter < n; iter++) {
        // Step 1: 找未访问中 dist 最小的节点 u
        let u = -1;
        let minDist = Infinity;
        for (let i = 0; i < n; i++) {
            if (!visited[i] && dist[i] < minDist) {
                minDist = dist[i];
                u = i;
            }
        }

        // 如果找不到(剩余点不可达),提前退出
        if (u === -1) break;

        // 标记为已访问
        visited[u] = true;

        // Step 2: 用 u 松弛所有邻居
        for (const [v, w] of adj[u]) {
            if (!visited[v] && dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
            }
        }
    }

    const maxTime = Math.max(...dist);
    console.log(maxTime === Infinity ? -1 : maxTime);
}


const cases = [
  `4
3
2 1 1
2 3 1
3 4 1
2`
];

let caseIndex = 0;
let lineIndex = 0;

const readline = (function () {
    let lines = [];
    return function () {
        if (lineIndex === 0) {
            lines = cases[caseIndex]
                .trim()
                .split("\n")
                .map((line) => line.trim());
        }
        return lines[lineIndex++];
    };
})();

cases.forEach((_, i) => {
    caseIndex = i;
    lineIndex = 0;
    solution();
});

参考代码二(最小堆)

// 手写最小堆(元素为 [distance, node])
class MinHeap {
    constructor() {
        this.heap = [];
    }

    push(val) {
        this.heap.push(val);
        this._bubbleUp(this.heap.length - 1);
    }

    pop() {
        if (this.heap.length === 0) return null;
        if (this.heap.length === 1) return this.heap.pop();

        const min = this.heap[0];
        this.heap[0] = this.heap.pop();
        this._bubbleDown(0);
        return min;
    }

    _bubbleUp(idx) {
        while (idx > 0) {
            const parent = Math.floor((idx - 1) / 2);
            if (this.heap[idx][0] >= this.heap[parent][0]) break;
            [this.heap[idx], this.heap[parent]] = [this.heap[parent], this.heap[idx]];
            idx = parent;
        }
    }

    _bubbleDown(idx) {
        const n = this.heap.length;
        while (true) {
            let smallest = idx;
            const left = 2 * idx + 1;
            const right = 2 * idx + 2;

            if (left < n && this.heap[left][0] < this.heap[smallest][0]) {
                smallest = left;
            }
            if (right < n && this.heap[right][0] < this.heap[smallest][0]) {
                smallest = right;
            }
            if (smallest === idx) break;

            [this.heap[idx], this.heap[smallest]] = [this.heap[smallest], this.heap[idx]];
            idx = smallest;
        }
    }

    isEmpty() {
        return this.heap.length === 0;
    }
}

function solution() {
    const n = parseInt(readline());
    const m = parseInt(readline());

    // 构建邻接表(0-based)
    const adj = Array.from({ length: n }, () => []);
    for (let i = 0; i < m; i++) {
        const [u, v, t] = readline().split(' ').map(Number);
        adj[u - 1].push([v - 1, t]);
    }

    const start = parseInt(readline()) - 1;

    // Dijkstra 初始化
    const dist = Array(n).fill(Infinity);
    dist[start] = 0;

    // 使用最小堆优化
    const heap = new MinHeap();
    heap.push([0, start]);

    while (!heap.isEmpty()) {
        const [currentDist, u] = heap.pop();

        // 跳过过期条目(已有更短路径)
        if (currentDist > dist[u]) continue;

        // 松弛相邻节点
        for (const [v, t] of adj[u]) {
            const newDist = dist[u] + t;
            if (newDist < dist[v]) {
                dist[v] = newDist;
                heap.push([newDist, v]);
            }
        }
    }

    const maxTime = Math.max(...dist);
    console.log(maxTime === Infinity ? -1 : maxTime);
}

const cases = [
  `4
3
2 1 1
2 3 1
3 4 1
2`
];

let caseIndex = 0;
let lineIndex = 0;

const readline = (function () {
    let lines = [];
    return function () {
        if (lineIndex === 0) {
            lines = cases[caseIndex]
                .trim()
                .split("\n")
                .map((line) => line.trim());
        }
        return lines[lineIndex++];
    };
})();

cases.forEach((_, i) => {
    caseIndex = i;
    lineIndex = 0;
    solution();
});

⚙️ 两种实现方式对比

特性朴素 Dijkstra最小堆优化 Dijkstra
数据结构数组 + visited 标记最小堆(优先队列)
找最小节点方式每次遍历所有节点 O(N)堆顶 O(1) 弹出
时间复杂度O(N²)O((N + M) log N)
空间复杂度O(N + M)O(N + M)
适用场景N ≤ 1000,稠密图(M ≈ N²)N 较大,稀疏图(M ≈ N)
代码复杂度⭐ 简单⭐⭐ 需实现堆
本题推荐✅ 更优(N ≤ 200)可用,但无必要

### 华为 OD 试题及解答 #### Java 题目解析 在华为 OD 考中,Java 是一种常见的编程语言。以下是一个关于二进制位操作的经典题目及其解决方案。 给定一个整数 `num`,计算其二进制表示中有多少个 `1` 的方法可以通过逐位检查实现[^2]。 以下是具体的代码示例: ```java import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); int num = in.nextInt(); // 输入数字 int count = 0; // 计数器初始化 for (int i = 0; i < 32; i++) { // 假设输入的是标准的 32 位整数 if ((num & 1) == 1) { // 如果当前最低位是 1,则增加计数 count++; } num = num >>> 1; // 将数字无符号右移一位 } System.out.println(count); // 输出最终的结果 } } ``` 此程序的核心在于利用按位与运算符 (`&`) 和无符号右移运算符 (`>>>`) 来逐步提取并统计二进制中的每一位是否为 `1`。 --- #### 字符串处理题目解析 另一个常见问题是字符串的操作,比如将字符串中的每个单词首字母大写化[^3]。下面是一段 Python 实现的例子: ```python while True: try: words = input().split() # 获取用户输入并分割成列表 result = [] for word in words: if len(word) > 0: # 确保单词长度大于零 capitalized_word = f"{word[0].upper()}{word[1:]}" result.append(capitalized_word) print(" ".join(result)) # 打印结果 except Exception as e: break # 捕获异常后退出循环 ``` 该脚本通过遍历每一个单词,并将其第一个字符转为大写字母来完成任务。 --- #### 准备建议 为了更好地应对华为 OD 试,可以采取如下策略: - **熟悉常用算法**:掌握基本的数据结构(数组、链表、堆栈等)以及经典算法(排序、查找等),这些知识点经常会在实际测试中被考察到。 - **多练习编码能力**:针对不同类型的题目进行专项训练,尤其是时间复杂度优化方面的技巧学习。 - **理解业务场景需求**:部分试题可能结合具体应用场景设计,因此除了技术层面外还需要考虑逻辑思维的应用。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值