跳转至

924. 尽量减少恶意软件的传播

题目描述

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

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

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

如果从 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. 这些节点中没有一个节点被感染
  2. 这些节点中只有一个节点被感染
  3. 这些节点中有多个节点被感染

我们要考虑的是,移除某个感染节点后,剩下的节点中被感染的节点数最少。

情况一没有被感染的节点,不需要考虑;情况二只有一个节点被感染,那么移除这个节点后,该连通分量中的其他节点都不会被感染;情况三有多个节点被感染,那么移除任意一个感染节点后,该连通分量中的其他节点还是会被感染,所以我们只需要考虑情况二。

我们利用并查集 $uf$ 维护节点的连通关系,用一个变量 $ans$ 记录答案,用一个变量 $mx$ 记录当前能减少感染的最大节点数,初始时 $ans = n$, $mx = 0$。

然后遍历数组 $initial$,用一个哈希表或者一个长度为 $n$ 的数组 $cnt$ 统计每个连通分量中被感染节点的个数。

接下来,我们再遍历数组 $initial$,对于每个节点 $x$,我们找到其所在的连通分量的根节点 $root$,如果该连通分量中只有一个被感染节点,即 $cnt[root] = 1$,我们就更新答案,更新的条件是该连通分量中的节点数 $sz$ 大于 $mx$ 或者 $sz$ 等于 $mx$ 且 $x$ 的值小于 $ans$。

最后,如果 $ans$ 没有被更新,说明所有的连通分量中都有多个被感染节点,那么我们返回 $initial$ 中的最小值,否则返回 $ans$。

时间复杂度 $O(n^2 \times \alpha(n))$,空间复杂度 $O(n)$。其中 $n$ 是节点的个数,而 $\alpha(n)$ 是 Ackermann 函数的反函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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)
        uf = UnionFind(n)
        for i in range(n):
            for j in range(i + 1, n):
                graph[i][j] and uf.union(i, j)
        cnt = Counter(uf.find(x) for x in initial)
        ans, mx = n, 0
        for x in initial:
            root = uf.find(x)
            if cnt[root] > 1:
                continue
            sz = uf.get_size(root)
            if sz > mx or (sz == mx and x < ans):
                ans = x
                mx = sz
        return min(initial) if ans == n else ans
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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;
        UnionFind uf = new UnionFind(n);
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n; ++j) {
                if (graph[i][j] == 1) {
                    uf.union(i, j);
                }
            }
        }
        int ans = n;
        int mi = n, mx = 0;
        int[] cnt = new int[n];
        for (int x : initial) {
            ++cnt[uf.find(x)];
            mi = Math.min(mi, x);
        }

        for (int x : initial) {
            int root = uf.find(x);
            if (cnt[root] == 1) {
                int sz = uf.size(root);
                if (sz > mx || (sz == mx && x < ans)) {
                    ans = x;
                    mx = sz;
                }
            }
        }
        return ans == n ? mi : ans;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
class UnionFind {
public:
    UnionFind(int n) {
        p = vector<int>(n);
        size = vector<int>(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<int> p, size;
};

class Solution {
public:
    int minMalwareSpread(vector<vector<int>>& graph, vector<int>& initial) {
        int n = graph.size();
        UnionFind uf(n);
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n; ++j) {
                if (graph[i][j]) {
                    uf.unite(i, j);
                }
            }
        }
        int ans = n, mx = 0;
        vector<int> cnt(n);
        for (int x : initial) {
            ++cnt[uf.find(x)];
        }
        for (int x : initial) {
            int root = uf.find(x);
            if (cnt[root] == 1) {
                int sz = uf.getSize(root);
                if (sz > mx || (sz == mx && ans > x)) {
                    ans = x;
                    mx = sz;
                }
            }
        }
        return ans == n ? *min_element(initial.begin(), initial.end()) : ans;
    }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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)
    uf := newUnionFind(n)
    for i := range graph {
        for j := i + 1; j < n; j++ {
            if graph[i][j] == 1 {
                uf.union(i, j)
            }
        }
    }
    cnt := make([]int, n)
    ans, mx := n, 0
    for _, x := range initial {
        cnt[uf.find(x)]++
    }
    for _, x := range initial {
        root := uf.find(x)
        if cnt[root] == 1 {
            sz := uf.getSize(root)
            if sz > mx || sz == mx && x < ans {
                ans, mx = x, sz
            }
        }
    }
    if ans == n {
        return slices.Min(initial)
    }
    return ans
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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 uf = new UnionFind(n);
    for (let i = 0; i < n; ++i) {
        for (let j = i + 1; j < n; ++j) {
            graph[i][j] && uf.union(i, j);
        }
    }
    let [ans, mx] = [n, 0];
    const cnt: number[] = Array(n).fill(0);
    for (const x of initial) {
        ++cnt[uf.find(x)];
    }
    for (const x of initial) {
        const root = uf.find(x);
        if (cnt[root] === 1) {
            const sz = uf.getSize(root);
            if (sz > mx || (sz === mx && x < ans)) {
                [ans, mx] = [x, sz];
            }
        }
    }
    return ans === n ? Math.min(...initial) : ans;
}

评论