Hard

题目描述

给你一个由 n 个节点组成的网络,用 n x n 个邻接矩阵 graph 表示,在节点网络中,只有当 graph[i][j] = 1 时,节点 i 和节点 j 之间有直接连接。

一些节点 initial 最初被恶意软件感染。只要两个节点直接相连,且其中至少一个节点受到恶意软件的感染,那么两个节点都将被恶意软件感染。这种恶意软件的传播将继续,直到没有更多的节点可以被这种方式感染。

假设 M(initial) 是在恶意软件停止传播之后,整个网络中被恶意软件感染的节点数。我们将从 initial 中恰好移除一个节点。

返回移除哪一个节点,能够最小化 M(initial)。如果移除多个节点都能够最小化 M(initial),就返回索引最小的节点。

注意,如果一个节点从 initial 的感染节点列表中移除,它可能仍然因为恶意软件传播而受到感染。

示例 1:

输入:graph = [[1,1,0],[1,1,0],[0,0,1]], initial = [0,1]
输出:0

示例 2:

输入:graph = [[1,0,0],[0,1,0],[0,0,1]], initial = [0,2]
输出:0

示例 3:

输入:graph = [[1,1,1],[1,1,1],[1,1,1]], initial = [1,2]
输出:1

提示:

  • n == graph.length
  • n == graph[i].length
  • 2 <= n <= 300
  • graph[i][j] 是 0 或 1
  • graph[i][j] == graph[j][i]
  • graph[i][i] == 1
  • 1 <= initial.length <= n
  • 0 <= initial[i] <= n - 1
  • initial 中的所有整数都是唯一的

解题思路

解题思路:

这道题的核心是理解恶意软件传播的机制:通过连通分量进行传播。我们需要找到移除哪个初始感染节点能最大化减少最终感染节点数。

分析过程:

  1. 连通分量分析:首先使用DFS或并查集找到图中的所有连通分量。在同一个连通分量内,如果有任何一个初始感染节点,整个连通分量都会被感染。

  2. 关键观察:移除一个初始感染节点只有在以下情况下才有意义:

    • 该节点是其所在连通分量中唯一的初始感染节点
    • 如果连通分量中有多个初始感染节点,移除其中一个不会影响该连通分量的感染状态
  3. 最优策略

    • 对于每个连通分量,统计其中的初始感染节点数量和总节点数量
    • 如果连通分量只有一个初始感染节点,移除它可以拯救整个连通分量
    • 在所有候选节点中,选择能拯救最多节点的那个
    • 如果拯救节点数相同,选择索引最小的

算法步骤:

  1. 使用DFS找到所有连通分量
  2. 对每个连通分量统计初始感染节点数和总节点数
  3. 计算移除每个初始感染节点能拯救的节点数
  4. 返回能拯救最多节点的初始感染节点(索引最小优先)

代码实现

class Solution {
public:
    int minMalwareSpread(vector<vector<int>>& graph, vector<int>& initial) {
        int n = graph.size();
        vector<bool> visited(n, false);
        vector<int> component(n, -1);
        vector<vector<int>> components;
        
        // 找到所有连通分量
        for (int i = 0; i < n; i++) {
            if (!visited[i]) {
                vector<int> comp;
                dfs(graph, i, visited, comp);
                for (int node : comp) {
                    component[node] = components.size();
                }
                components.push_back(comp);
            }
        }
        
        // 统计每个连通分量中的初始感染节点数
        vector<int> infectedCount(components.size(), 0);
        set<int> initialSet(initial.begin(), initial.end());
        
        for (int node : initial) {
            infectedCount[component[node]]++;
        }
        
        int maxSaved = -1;
        int result = *min_element(initial.begin(), initial.end());
        
        // 对每个初始感染节点计算移除后能拯救的节点数
        for (int node : initial) {
            int comp = component[node];
            int saved = 0;
            
            // 只有当该节点是连通分量中唯一的初始感染节点时才有意义
            if (infectedCount[comp] == 1) {
                saved = components[comp].size();
            }
            
            if (saved > maxSaved || (saved == maxSaved && node < result)) {
                maxSaved = saved;
                result = node;
            }
        }
        
        return result;
    }
    
private:
    void dfs(vector<vector<int>>& graph, int node, vector<bool>& visited, vector<int>& component) {
        visited[node] = true;
        component.push_back(node);
        
        for (int i = 0; i < graph.size(); i++) {
            if (graph[node][i] == 1 && !visited[i]) {
                dfs(graph, i, visited, component);
            }
        }
    }
};
class Solution:
    def minMalwareSpread(self, graph: List[List[int]], initial: List[int]) -> int:
        n = len(graph)
        visited = [False] * n
        component = [-1] * n
        components = []
        
        # 找到所有连通分量
        for i in range(n):
            if not visited[i]:
                comp = []
                self.dfs(graph, i, visited, comp)
                for node in comp:
                    component[node] = len(components)
                components.append(comp)
        
        # 统计每个连通分量中的初始感染节点数
        infected_count = [0] * len(components)
        initial_set = set(initial)
        
        for node in initial:
            infected_count[component[node]] += 1
        
        max_saved = -1
        result = min(initial)
        
        # 对每个初始感染节点计算移除后能拯救的节点数
        for node in initial:
            comp = component[node]
            saved = 0
            
            # 只有当该节点是连通分量中唯一的初始感染节点时才有意义
            if infected_count[comp] == 1:
                saved = len(components[comp])
            
            if saved > max_saved or (saved == max_saved and node < result):
                max_saved = saved
                result = node
        
        return result
    
    def dfs(self, graph, node, visited, component):
        visited[node] = True
        component.append(node)
        
        for i in range(len(graph)):
            if graph[node][i] == 1 and not visited[i]:
                self.dfs(graph, i, visited, component)
public class Solution {
    public int MinMalwareSpread(int[][] graph, int[] initial) {
        int n = graph.Length;
        bool[] visited = new bool[n];
        int[] component = new int[n];
        List<List<int>> components = new List<List<int>>();
        
        // 初始化component数组
        for (int i = 0; i < n; i++) {
            component[i] = -1;
        }
        
        // 找到所有连通分量
        for (int i = 0; i < n; i++) {
            if (!visited[i]) {
                List<int> comp = new List<int>();
                DFS(graph, i, visited, comp);
                foreach (int node in comp) {
                    component[node] = components.Count;
                }
                components.Add(comp);
            }
        }
        
        // 统计每个连通分量中的初始感染节点数
        int[] infectedCount = new int[components.Count];
        HashSet<int> initialSet = new HashSet<int>(initial);
        
        foreach (int node in initial) {
            infectedCount[component[node]]++;
        }
        
        int maxSaved = -1;
        int result = initial.Min();
        
        // 对每个初始感染节点计算移除后能拯救的节点数
        foreach (int node in initial) {
            int comp = component[node];
            int saved = 0;
            
            // 只有当该节点是连通分量中唯一的初始感染节点时才有意义
            if (infectedCount[comp] == 1) {
                saved = components[comp].Count;
            }
            
            if (saved > maxSaved || (saved == maxSaved && node < result)) {
                maxSaved = saved;
                result = node;
            }
        }
        
        return result;
    }
    
    private void DFS(int[][] graph, int node, bool[] visited, List<int> component) {
        visited[node] = true;
        component.Add(node);
        
        for (int i = 0; i < graph.Length; i++) {
            if (graph[node][i] == 1 && !visited[i]) {
                DFS(graph, i, visited, component);
            }
        }
    }
}
var minMalwareSpread = function(graph, initial) {
    const n = graph.length;
    const visited = new Array(n).fill(false);
    const component = new Array(n).fill(-1);
    const components = [];
    
    // 找到所有连通分量
    for (let i = 0; i < n; i++) {
        if (!visited[i]) {
            const comp = [];
            dfs(graph, i, visited, comp);
            for (const node of comp) {
                component[node] = components.length;
            }
            components.push(comp);
        }
    }
    
    // 统计每个连通分量中的初始感染节点数
    const infectedCount = new Array(components.length).fill(0);
    const initialSet = new Set(initial);
    
    for (const node of initial) {
        infectedCount[component[node]]++;
    }
    
    let maxSaved = -1;
    let result = Math.min(...initial);
    
    // 对每个初始感染节点计算移除后能拯救的节点数
    for (const node of initial) {
        const comp = component[node];
        let saved = 0;
        
        // 只有当该节点是连通分量中唯一的初始感染节点时才有意义
        if (infectedCount[comp]

复杂度分析

复杂度类型
时间复杂度O(n²)
空间复杂度O(n)

时间复杂度分析:

  • DFS遍历图需要O(n²)时间,因为需要检查所有边
  • 统计连通分量和计算结果需要O(n)时间
  • 总时间复杂度为O(n²)

空间复杂度分析:

  • visited数组、component数组各需要O(n)空间
  • components列表最多存储n个节点,需要O(n)空间
  • DFS递归栈最深为O(n)
  • 总空间复杂度为O(n)