--- comments: true difficulty: 困难 edit_url: https://github.com/doocs/leetcode/edit/main/solution/0900-0999/0928.Minimize%20Malware%20Spread%20II/README.md tags: - 深度优先搜索 - 广度优先搜索 - 并查集 - 图 - 数组 - 哈希表 --- # [928. 尽量减少恶意软件的传播 II](https://leetcode.cn/problems/minimize-malware-spread-ii) [English Version](/solution/0900-0999/0928.Minimize%20Malware%20Spread%20II/README_EN.md) ## 题目描述

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

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

假设 M(initial) 是在恶意软件停止传播之后,整个网络中感染恶意软件的最终节点数。

我们可以从 initial 中 删除一个节点并完全移除该节点以及从该节点到任何其他节点的任何连接。

请返回移除后能够使 M(initial) 最小化的节点。如果有多个节点满足条件,返回索引 最小的节点

 

示例 1:

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

示例 2:

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

示例 3:

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

 

提示:

## 解法 ### 方法一:并查集 我们可以使用并查集,将所有不在 $initial$ 中的节点,并且满足 $graph[i][j] = 1$ 的节点 $(i, j)$ 进行合并。 接下来,我们创建一个哈希表 $g$,其中 $g[i]$ 表示所有与节点 $i$ 相连的连通分量的根节点。我们还需要一个计数器 $cnt$,用来统计每个根节点被多少个初始节点感染。 对于每个初始时被感染的节点 $i$,我们遍历所有与节点 $i$ 相连的节点 $j$,如果节点 $j$ 不在 $initial$ 中,我们将节点 $j$ 的根节点加入到集合 $g[i]$ 中。同时,我们统计每个根节点被多少个初始节点感染,将结果保存到计数器 $cnt$ 中。 然后,我们用一个变量 $ans$ 记录答案,用 $mx$ 记录最多可以减少的被感染节点的数量。初始时 $ans = 0$, $mx = -1$。 遍历所有初始时被感染的节点,对于每个节点 $i$,我们遍历 $g[i]$ 中的所有根节点,如果根节点只被一个初始节点感染,我们累加这个根节点所在的连通分量的大小到 $t$ 中。如果 $t > mx$ 或者 $t = mx$ 且 $i < ans$,我们更新 $ans = i$, $mx = t$。 最后返回 $ans$ 即可。 时间复杂度 $O(n^2 \times \alpha(n))$,空间复杂度 $O(n^2)$。其中 $n$ 是节点的数量,而 $\alpha(n)$ 是 Ackermann 函数的反函数。 #### Python3 ```python class UnionFind: __slots__ = "p", "size" def __init__(self, n: int): self.p = list(range(n)) self.size = [1] * n def find(self, x: int) -> int: if self.p[x] != x: self.p[x] = self.find(self.p[x]) return self.p[x] def union(self, a: int, b: int) -> bool: pa, pb = self.find(a), self.find(b) if pa == pb: return False if self.size[pa] > self.size[pb]: self.p[pb] = pa self.size[pa] += self.size[pb] else: self.p[pa] = pb self.size[pb] += self.size[pa] return True def get_size(self, root: int) -> int: return self.size[root] class Solution: def minMalwareSpread(self, graph: List[List[int]], initial: List[int]) -> int: n = len(graph) s = set(initial) uf = UnionFind(n) for i in range(n): if i not in s: for j in range(i + 1, n): graph[i][j] and j not in s and uf.union(i, j) g = defaultdict(set) cnt = Counter() for i in initial: for j in range(n): if j not in s and graph[i][j]: g[i].add(uf.find(j)) for root in g[i]: cnt[root] += 1 ans, mx = 0, -1 for i in initial: t = sum(uf.get_size(root) for root in g[i] if cnt[root] == 1) if t > mx or (t == mx and i < ans): ans, mx = i, t return ans ``` #### Java ```java class UnionFind { private final int[] p; private final int[] size; public UnionFind(int n) { p = new int[n]; size = new int[n]; for (int i = 0; i < n; ++i) { p[i] = i; size[i] = 1; } } public int find(int x) { if (p[x] != x) { p[x] = find(p[x]); } return p[x]; } public boolean union(int a, int b) { int pa = find(a), pb = find(b); if (pa == pb) { return false; } if (size[pa] > size[pb]) { p[pb] = pa; size[pa] += size[pb]; } else { p[pa] = pb; size[pb] += size[pa]; } return true; } public int size(int root) { return size[root]; } } class Solution { public int minMalwareSpread(int[][] graph, int[] initial) { int n = graph.length; boolean[] s = new boolean[n]; for (int i : initial) { s[i] = true; } UnionFind uf = new UnionFind(n); for (int i = 0; i < n; ++i) { if (!s[i]) { for (int j = i + 1; j < n; ++j) { if (graph[i][j] == 1 && !s[j]) { uf.union(i, j); } } } } Set[] g = new Set[n]; Arrays.setAll(g, k -> new HashSet<>()); int[] cnt = new int[n]; for (int i : initial) { for (int j = 0; j < n; ++j) { if (!s[j] && graph[i][j] == 1) { g[i].add(uf.find(j)); } } for (int root : g[i]) { ++cnt[root]; } } int ans = 0, mx = -1; for (int i : initial) { int t = 0; for (int root : g[i]) { if (cnt[root] == 1) { t += uf.size(root); } } if (t > mx || (t == mx && i < ans)) { ans = i; mx = t; } } return ans; } } ``` #### C++ ```cpp class UnionFind { public: UnionFind(int n) { p = vector(n); size = vector(n, 1); iota(p.begin(), p.end(), 0); } bool unite(int a, int b) { int pa = find(a), pb = find(b); if (pa == pb) { return false; } if (size[pa] > size[pb]) { p[pb] = pa; size[pa] += size[pb]; } else { p[pa] = pb; size[pb] += size[pa]; } return true; } int find(int x) { if (p[x] != x) { p[x] = find(p[x]); } return p[x]; } int getSize(int root) { return size[root]; } private: vector p, size; }; class Solution { public: int minMalwareSpread(vector>& graph, vector& initial) { int n = graph.size(); bool s[n]; memset(s, false, sizeof(s)); for (int i : initial) { s[i] = true; } UnionFind uf(n); for (int i = 0; i < n; ++i) { if (!s[i]) { for (int j = i + 1; j < n; ++j) { if (graph[i][j] && !s[j]) { uf.unite(i, j); } } } } unordered_set g[n]; int cnt[n]; memset(cnt, 0, sizeof(cnt)); for (int i : initial) { for (int j = 0; j < n; ++j) { if (!s[j] && graph[i][j]) { g[i].insert(uf.find(j)); } } for (int root : g[i]) { ++cnt[root]; } } int ans = 0, mx = -1; for (int i : initial) { int t = 0; for (int root : g[i]) { if (cnt[root] == 1) { t += uf.getSize(root); } } if (t > mx || (t == mx && i < ans)) { ans = i; mx = t; } } return ans; } }; ``` #### Go ```go type unionFind struct { p, size []int } func newUnionFind(n int) *unionFind { p := make([]int, n) size := make([]int, n) for i := range p { p[i] = i size[i] = 1 } return &unionFind{p, size} } func (uf *unionFind) find(x int) int { if uf.p[x] != x { uf.p[x] = uf.find(uf.p[x]) } return uf.p[x] } func (uf *unionFind) union(a, b int) bool { pa, pb := uf.find(a), uf.find(b) if pa == pb { return false } if uf.size[pa] > uf.size[pb] { uf.p[pb] = pa uf.size[pa] += uf.size[pb] } else { uf.p[pa] = pb uf.size[pb] += uf.size[pa] } return true } func (uf *unionFind) getSize(root int) int { return uf.size[root] } func minMalwareSpread(graph [][]int, initial []int) int { n := len(graph) s := make([]bool, n) for _, i := range initial { s[i] = true } uf := newUnionFind(n) for i := range graph { if !s[i] { for j := i + 1; j < n; j++ { if graph[i][j] == 1 && !s[j] { uf.union(i, j) } } } } g := make([]map[int]bool, n) for _, i := range initial { g[i] = map[int]bool{} } cnt := make([]int, n) for _, i := range initial { for j := 0; j < n; j++ { if !s[j] && graph[i][j] == 1 { g[i][uf.find(j)] = true } } for root := range g[i] { cnt[root]++ } } ans, mx := 0, -1 for _, i := range initial { t := 0 for root := range g[i] { if cnt[root] == 1 { t += uf.getSize(root) } } if t > mx || t == mx && i < ans { ans, mx = i, t } } return ans } ``` #### TypeScript ```ts class UnionFind { p: number[]; size: number[]; constructor(n: number) { this.p = Array(n) .fill(0) .map((_, i) => i); this.size = Array(n).fill(1); } find(x: number): number { if (this.p[x] !== x) { this.p[x] = this.find(this.p[x]); } return this.p[x]; } union(a: number, b: number): boolean { const [pa, pb] = [this.find(a), this.find(b)]; if (pa === pb) { return false; } if (this.size[pa] > this.size[pb]) { this.p[pb] = pa; this.size[pa] += this.size[pb]; } else { this.p[pa] = pb; this.size[pb] += this.size[pa]; } return true; } getSize(root: number): number { return this.size[root]; } } function minMalwareSpread(graph: number[][], initial: number[]): number { const n = graph.length; const s = new Set(initial); const uf = new UnionFind(n); for (let i = 0; i < n; ++i) { if (!s.has(i)) { for (let j = i + 1; j < n; ++j) { if (graph[i][j] && !s.has(j)) { uf.union(i, j); } } } } const g: Set[] = Array.from({ length: n }, () => new Set()); const cnt: number[] = Array(n).fill(0); for (const i of initial) { for (let j = 0; j < n; ++j) { if (graph[i][j] && !s.has(j)) { g[i].add(uf.find(j)); } } for (const root of g[i]) { ++cnt[root]; } } let ans = 0; let mx = -1; for (const i of initial) { let t = 0; for (const root of g[i]) { if (cnt[root] === 1) { t += uf.getSize(root); } } if (t > mx || (t === mx && i < ans)) { [ans, mx] = [i, t]; } } return ans; } ```