【Leetcode】928. Minimize Malware Spread II
技術標籤:LC DFS、BFS與圖論leetcode圖論dfs雜湊表資料結構
題目地址:
https://leetcode.com/problems/minimize-malware-spread-ii/
給定一個 n n n個節點的無向圖,以鄰接矩陣給出。每個節點代表一個網路中的節點。再給定一個數組 A A A, A [ i ] A[i] A[i]表示節點 i i i被感染了(下文稱 A A A裡的點是”感染源頭“)。當一個節點被感染了,它所在的連通塊的所有節點都會被感染。現在允許選取一個“感染源頭”,將其刪掉(刪掉之後,這個感染源和其所有鄰接邊都會被刪除)。問將哪個節點刪掉可以使得剩餘節點的總的被感染節點數量最小。如果有多個答案,則返回編號最小的節點。
這個問題可以和https://blog.csdn.net/qq_46105170/article/details/113488695一起看,兩者解答可以互相借鑑。DFS做法參考https://blog.csdn.net/qq_46105170/article/details/113491096。下面介紹並查集做法。
我們主要是要考慮刪掉某個感染源後,能“拯救”多少個節點。我們可以先不考慮感染源,然後用並查集統計連通塊。接著,我們用雜湊表統計一下每個連通塊被多少個感染源感染了,略過沒有被感染的連通塊,如果其被多於
1
1
1個感染源感染了,那麼無論刪掉哪個感染源該連通塊都無法被拯救,這樣的連通塊就不考慮了;如果其恰好是被
1
1
import java.util.*;
public class Solution {
class UnionFind {
int[] parent, size;
public UnionFind(int size) {
parent = new int[size];
for (int i = 0; i < size; i++) {
parent[i] = i;
}
this.size = new int[size];
Arrays.fill(this.size, 1);
}
public int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
public void union(int x, int y) {
int px = find(x), py = find(y);
if (px == py) {
return;
}
parent[px] = py;
size[py] += size[px];
}
}
public int minMalwareSpread(int[][] graph, int[] initial) {
int n = graph.length;
// 記錄一下哪些點是感染源
boolean[] dirty = new boolean[n];
for (int x : initial) {
dirty[x] = true;
}
// 先不考慮感染源,求一下連通塊
UnionFind uf = new UnionFind(n);
for (int i = 0; i < n; i++) {
if (!dirty[i]) {
for (int j = i + 1; j < n; j++) {
if (!dirty[j] && graph[i][j] == 1) {
uf.union(i, j);
}
}
}
}
// 求一下每個連通塊被哪些感染源感染了,key存連通塊的樹根,value存能感染到該連通塊的感染源編號
Map<Integer, Set<Integer>> infectorMap = new HashMap<>();
for (int x : initial) {
for (int y = 0; y < graph[x].length; y++) {
// 如果通過x能感染到y,則用雜湊表存一下
if (!dirty[y] && graph[x][y] == 1) {
int py = uf.find(y);
infectorMap.putIfAbsent(py, new HashSet<>());
infectorMap.get(py).add(x);
}
}
}
// 求一下每個感染源如果將其刪掉,能拯救多少個節點。
// 注意這裡要略過被多個感染源感染的連通塊,這些連通塊是無法被拯救的
Map<Integer, Integer> saveCount = new HashMap<>();
for (Map.Entry<Integer, Set<Integer>> entry : infectorMap.entrySet()) {
int parent = entry.getKey();
Set<Integer> infectors = entry.getValue();
// 如果當前連通塊被多個感染源感染了,那就略過
if (infectors.size() > 1) {
continue;
}
// 累加一下這個感染源如果刪掉能拯救多少個節點
int infector = infectors.iterator().next();
saveCount.put(infector, saveCount.getOrDefault(infector, 0) + uf.size[parent]);
}
// 如果每個連通塊都被多於1個感染源感染了,那就刪掉編號最小的感染源
if (saveCount.isEmpty()) {
int res = n;
for (int i : initial) {
res = Math.min(res, i);
}
return res;
}
// 否則去找一下被拯救節點最多的那個感染源
int res = -1, maxSave = 0;
for (Map.Entry<Integer, Integer> entry : saveCount.entrySet()) {
int infector = entry.getKey(), saved = entry.getValue();
if (saved > maxSave) {
maxSave = saved;
res = infector;
} else if (saved == maxSave) {
res = Math.min(res, infector);
}
}
return res;
}
}
時間複雜度 O ( n 2 log ∗ n ) O(n^2\log ^* n) O(n2log∗n),空間 O ( n ) O(n) O(n)。