加载中...
928-尽量减少恶意软件的传播 II(Minimize Malware Spread II)
发表于:2021-12-03 | 分类: 困难
字数统计: 2k | 阅读时长: 10分钟 | 阅读量:

原文链接: https://leetcode-cn.com/problems/minimize-malware-spread-ii

英文原文

You are given a network of n nodes represented as an n x n adjacency matrix graph, where the ith node is directly connected to the jth node if graph[i][j] == 1.

Some nodes initial are initially infected by malware. Whenever two nodes are directly connected, and at least one of those two nodes is infected by malware, both nodes will be infected by malware. This spread of malware will continue until no more nodes can be infected in this manner.

Suppose M(initial) is the final number of nodes infected with malware in the entire network after the spread of malware stops.

We will remove exactly one node from initial, completely removing it and any connections from this node to any other node.

Return the node that, if removed, would minimize M(initial). If multiple nodes could be removed to minimize M(initial), return such a node with the smallest index.

 

Example 1:

Input: graph = [[1,1,0],[1,1,0],[0,0,1]], initial = [0,1]
Output: 0

Example 2:

Input: graph = [[1,1,0],[1,1,1],[0,1,1]], initial = [0,1]
Output: 1

Example 3:

Input: graph = [[1,1,0,0],[1,1,1,0],[0,1,1,1],[0,0,1,1]], initial = [0,1]
Output: 1

 

Constraints:

  • n == graph.length
  • n == graph[i].length
  • 2 <= n <= 300
  • graph[i][j] is 0 or 1.
  • graph[i][j] == graph[j][i]
  • graph[i][i] == 1
  • 1 <= initial.length < n
  • 0 <= initial[i] <= n - 1
  • All the integers in initial are unique.

中文题目

(这个问题与 尽量减少恶意软件的传播 是一样的,不同之处用粗体表示。)

在节点网络中,只有当 graph[i][j] = 1 时,每个节点 i 能够直接连接到另一个节点 j

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

假设 M(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

 

提示:

  1. 1 < graph.length = graph[0].length <= 300
  2. 0 <= graph[i][j] == graph[j][i] <= 1
  3. graph[i][i] = 1
  4. 1 <= initial.length < graph.length
  5. 0 <= initial[i] < graph.length

通过代码

官方题解

方法一: 深度优先搜索

思路和算法

首先构建一个图 G,其节点为所有不在 initial 中的剩余节点。

对于不在 initial 中的节点 v,检查会被 initial 中哪些节点 u 感染。 之后再看哪些节点 v 只会被一个节点 u 感染。具体的算法可以看代码中的注释。

[solution1-Java]
class Solution { public int minMalwareSpread(int[][] graph, int[] initial) { int N = graph.length; int[] clean = new int[N]; Arrays.fill(clean, 1); for (int x: initial) clean[x] = 0; // For each node u in initial, dfs to find // 'seen': all nodes not in initial that it can reach. ArrayList<Integer>[] infectedBy = new ArrayList[N]; for (int i = 0; i < N; ++i) infectedBy[i] = new ArrayList(); for (int u: initial) { Set<Integer> seen = new HashSet(); dfs(graph, clean, u, seen); for (int v: seen) infectedBy[v].add(u); } // For each node u in initial, for every v not in initial // that is uniquely infected by u, add 1 to the contribution for u. int[] contribution = new int[N]; for (int v = 0; v < N; ++v) if (infectedBy[v].size() == 1) contribution[infectedBy[v].get(0)]++; // Take the best answer. Arrays.sort(initial); int ans = initial[0], ansSize = -1; for (int u: initial) { int score = contribution[u]; if (score > ansSize || score == ansSize && u < ans) { ans = u; ansSize = score; } } return ans; } public void dfs(int[][] graph, int[] clean, int u, Set<Integer> seen) { for (int v = 0; v < graph.length; ++v) if (graph[u][v] == 1 && clean[v] == 1 && !seen.contains(v)) { seen.add(v); dfs(graph, clean, v, seen); } } }
[solution1-Python]
class Solution(object): def minMalwareSpread(self, graph, initial): N = len(graph) clean = set(range(N)) - set(initial) def dfs(u, seen): for v, adj in enumerate(graph[u]): if adj and v in clean and v not in seen: seen.add(v) dfs(v, seen) # For each node u in initial, dfs to find # 'seen': all nodes not in initial that it can reach. infected_by = {v: [] for v in clean} for u in initial: seen = set() dfs(u, seen) # For each node v that was seen, u infects v. for v in seen: infected_by[v].append(u) # For each node u in initial, for every v not in initial # that is uniquely infected by u, add 1 to the contribution for u. contribution = collections.Counter() for v, neighbors in infected_by.iteritems(): if len(neighbors) == 1: contribution[neighbors[0]] += 1 # Take the best answer. best = (-1, min(initial)) for u, score in contribution.iteritems(): if score > best[0] or score == best[0] and u < best[1]: best = score, u return best[1]

复杂度分析

  • 时间复杂度: $O(N^2)$,其中 $N$ 为 graph 的大小。

  • 空间复杂度: $O(N)$。

方法二: 并查集

思路

对于并查集中的一个集合,集合中会有一定数量的节点在 initial 里面,只需要关注那些只有一个 initial 节点的集合就可以了。

算法

首先构建一个图 G,其节点为所有不在 initial 中的剩余节点。然后用并查集找出所有的连通分量。

对于原始图中的每条边 n => vuinitial 中的节点,v 为不在 initial 中的节点。对于 initial 中的每个节点 u,如果 u 所在的集合中只有它是唯一的 initial 节点,那么这个集合的大小就是移除 u 之后能得到的收益。

之后遍历所有的可能找到最终答案。

[solution2-Java]
class Solution { public int minMalwareSpread(int[][] graph, int[] initial) { int N = graph.length; DSU dsu = new DSU(N); // clean[u] == 1 if its a node in the graph not in initial. int[] clean = new int[N]; Arrays.fill(clean, 1); for (int x: initial) clean[x] = 0; for (int u = 0; u < N; ++u) if (clean[u] == 1) for (int v = 0; v < N; ++v) if (clean[v] == 1) if (graph[u][v] == 1) dsu.union(u, v); // dsu now represents the components of the graph without // any nodes from initial. Let's call this graph G. int[] count = new int[N]; Map<Integer, Set<Integer>> nodeToCompo = new HashMap(); for (int u: initial) { Set<Integer> components = new HashSet(); for (int v = 0; v < N; ++v) if (clean[v] == 1) { if (graph[u][v] == 1) components.add(dsu.find(v)); } nodeToCompo.put(u, components); for (int c: components) count[c]++; } // For each node u in initial, nodeToCompo.get(u) // now has every component from G that u neighbors. int ans = -1, ansSize = -1; for (int u: nodeToCompo.keySet()) { Set<Integer> components = nodeToCompo.get(u); int score = 0; for (int c: components) if (count[c] == 1) // uniquely infected score += dsu.size(c); if (score > ansSize || score == ansSize && u < ans) { ansSize = score; ans = u; } } return ans; } } class DSU { int[] p, sz; DSU(int N) { p = new int[N]; for (int x = 0; x < N; ++x) p[x] = x; sz = new int[N]; Arrays.fill(sz, 1); } public int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; } public void union(int x, int y) { int xr = find(x); int yr = find(y); p[xr] = yr; sz[yr] += sz[xr]; } public int size(int x) { return sz[find(x)]; } }
[solutiion2-Python]
class DSU: def __init__(self, N): self.p = range(N) self.sz = [1] * N def find(self, x): if self.p[x] != x: self.p[x] = self.find(self.p[x]) return self.p[x] def union(self, x, y): xr = self.find(x) yr = self.find(y) self.p[xr] = yr self.sz[yr] += self.sz[xr] def size(self, x): return self.sz[self.find(x)] class Solution(object): def minMalwareSpread(self, graph, initial): N = len(graph) initial_set = set(initial) clean = [x for x in range(N) if x not in initial_set] # clean[u] == 1 if its a node in the graph not in initial. dsu = DSU(N) for u in clean: for v in clean: if graph[u][v]: dsu.union(u, v) # dsu now represents the components of the graph without # any nodes from initial. Let's call this graph G. count = collections.Counter() node_to_compo = {} for u in initial: components = set() for v in clean: if graph[u][v]: components.add(dsu.find(v)) node_to_compo[u] = components for c in components: count[c] += 1 # For each node u in initial, nodeToCompo.get(u) # now has every component from G that u neighbors. best = (-1, None) # score, node for u, components in node_to_compo.iteritems(): score = 0 for c in components: if count[c] == 1: #uniquely infected score += dsu.size(c) if score > best[0] or score == best[0] and u < best[1]: best = (score, u) return best[1]

复杂度分析

  • 时间复杂度: $O(N^2)$,其中 $N$ 为 graph 的大小。

  • 空间复杂度: $O(N)$。

统计信息

通过次数 提交次数 AC比率
2336 5532 42.2%

提交历史

提交时间 提交结果 执行时间 内存消耗 语言
上一篇:
927-三等分(Three Equal Parts)
下一篇:
931-下降路径最小和(Minimum Falling Path Sum)
本文目录
本文目录