Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

928. Minimize Malware Spread II - Leetcode Solution

Code Implementation

class Solution:
    def minMalwareSpread(self, graph, initial):
        n = len(graph)
        initial_set = set(initial)
        # Find connected components using Union Find
        parent = [i for i in range(n)]

        def find(u):
            while parent[u] != u:
                parent[u] = parent[parent[u]]
                u = parent[u]
            return u

        def union(u, v):
            pu, pv = find(u), find(v)
            if pu != pv:
                parent[pu] = pv

        # Build union-find structure
        for i in range(n):
            for j in range(i+1, n):
                if graph[i][j]:
                    union(i, j)

        # Count size and malware in each component
        size = [0]*n
        malware = [0]*n
        for i in range(n):
            root = find(i)
            size[root] += 1
        for node in initial:
            root = find(node)
            malware[root] += 1

        # Try removing each initial node
        result = None
        max_saved = -1
        for node in sorted(initial):
            root = find(node)
            if malware[root] == 1:
                saved = size[root]
                if saved > max_saved or (saved == max_saved and node < result):
                    max_saved = saved
                    result = node
        if result is None:
            result = min(initial)
        return result
      
class Solution {
public:
    int minMalwareSpread(vector<vector<int>>& graph, vector<int>& initial) {
        int n = graph.size();
        vector<int> parent(n);
        for (int i = 0; i < n; ++i) parent[i] = i;
        function<int(int)> find = [&](int u) {
            if (parent[u] != u) parent[u] = find(parent[u]);
            return parent[u];
        };
        auto unite = [&](int u, int v) {
            int pu = find(u), pv = find(v);
            if (pu != pv) parent[pu] = pv;
        };
        for (int i = 0; i < n; ++i)
            for (int j = i + 1; j < n; ++j)
                if (graph[i][j]) unite(i, j);

        vector<int> size(n, 0), malware(n, 0);
        for (int i = 0; i < n; ++i) size[find(i)]++;
        for (int node : initial) malware[find(node)]++;

        int result = -1, max_saved = -1;
        sort(initial.begin(), initial.end());
        for (int node : initial) {
            int root = find(node);
            if (malware[root] == 1) {
                if (size[root] > max_saved) {
                    max_saved = size[root];
                    result = node;
                }
            }
        }
        if (result == -1)
            result = *min_element(initial.begin(), initial.end());
        return result;
    }
};
      
class Solution {
    public int minMalwareSpread(int[][] graph, int[] initial) {
        int n = graph.length;
        int[] parent = new int[n];
        for (int i = 0; i < n; ++i) parent[i] = i;
        // Union Find
        for (int i = 0; i < n; ++i)
            for (int j = i + 1; j < n; ++j)
                if (graph[i][j] == 1) union(parent, i, j);

        int[] size = new int[n];
        int[] malware = new int[n];
        for (int i = 0; i < n; ++i) size[find(parent, i)]++;
        for (int node : initial) malware[find(parent, node)]++;

        int result = -1, maxSaved = -1;
        Arrays.sort(initial);
        for (int node : initial) {
            int root = find(parent, node);
            if (malware[root] == 1) {
                if (size[root] > maxSaved) {
                    maxSaved = size[root];
                    result = node;
                }
            }
        }
        if (result == -1)
            result = initial[0];
        return result;
    }
    private int find(int[] parent, int u) {
        if (parent[u] != u) parent[u] = find(parent, parent[u]);
        return parent[u];
    }
    private void union(int[] parent, int u, int v) {
        int pu = find(parent, u), pv = find(parent, v);
        if (pu != pv) parent[pu] = pv;
    }
}
      
var minMalwareSpread = function(graph, initial) {
    const n = graph.length;
    const parent = Array.from({length: n}, (_, i) => i);

    function find(u) {
        while (parent[u] !== u) {
            parent[u] = parent[parent[u]];
            u = parent[u];
        }
        return u;
    }

    function union(u, v) {
        const pu = find(u), pv = find(v);
        if (pu !== pv) parent[pu] = pv;
    }

    for (let i = 0; i < n; ++i)
        for (let j = i + 1; j < n; ++j)
            if (graph[i][j]) union(i, j);

    const size = Array(n).fill(0), malware = Array(n).fill(0);
    for (let i = 0; i < n; ++i) size[find(i)]++;
    for (const node of initial) malware[find(node)]++;

    let result = null, maxSaved = -1;
    initial.sort((a, b) => a - b);
    for (const node of initial) {
        const root = find(node);
        if (malware[root] === 1) {
            if (size[root] > maxSaved) {
                maxSaved = size[root];
                result = node;
            }
        }
    }
    if (result === null)
        result = Math.min(...initial);
    return result;
};
      

Problem Description

Given a network of n computers represented by an n x n adjacency matrix graph, some computers are initially infected with malware, listed in the array initial. Malware spreads to directly connected computers unless one infected node is removed from the network before the spread begins. Your task is to choose exactly one node from initial to remove, so that the final number of infected nodes is minimized. If there are multiple choices, return the node with the smallest index.

  • Each node is connected to itself (i.e., graph[i][i] = 1).
  • There is only one valid solution per input.
  • You may not reuse or remove more than one element from initial.

Thought Process

At first glance, it may seem like we should try removing each infected node and simulate the malware spread for each case. However, this brute-force approach is inefficient for large networks. To optimize, we need to understand how malware propagates: it spreads within connected components of the graph. If a component contains more than one initially infected node, removing any single node won’t stop the rest from infecting the component. But if a component has exactly one infected node, removing it will save the entire component from infection. This realization suggests we should focus on the structure of connected components and the distribution of infected nodes within them.

Solution Approach

  • 1. Identify Connected Components: Use Union-Find (Disjoint Set Union) to group nodes that are directly or indirectly connected. Each group is a connected component.
  • 2. Count Component Sizes: For each component, count how many nodes it contains.
  • 3. Count Infected Nodes per Component: For each component, count how many initial infected nodes it has.
  • 4. Evaluate Removal Impact: For each node in initial, if its component contains only that single infected node, removing it will prevent the entire component from being infected. The number of nodes saved equals the component’s size.
  • 5. Select the Best Node: Among all such nodes, choose the one that saves the most nodes. If there’s a tie, choose the node with the smallest index.
  • 6. Fallback: If no such node exists (i.e., all components have multiple infected nodes), return the node with the smallest index from initial.

We use Union-Find for efficiency, as it allows us to quickly determine which component a node belongs to and to merge groups as we process the adjacency matrix.

Example Walkthrough

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

  1. There are 3 nodes. Nodes 0 and 1 are connected, node 2 is isolated.
  2. Union-Find groups: [0,1] together, [2] alone.
  3. Component sizes: [0,1] has size 2, [2] has size 1.
  4. Initial infected: [0,1] both in the same component, so that component has 2 infected nodes.
  5. Since there are no components with exactly one infected node, we cannot save any nodes by removing just one. According to the rules, we return the node with the smallest index in initial, which is 0.

Another example:
graph = [[1,0,0],[0,1,0],[0,0,1]]
initial = [0,2]

  • Each node is its own component of size 1.
  • Each component has exactly one infected node.
  • Removing node 0 saves 1 node (component 0), removing node 2 saves 1 node (component 2).
  • Both save the same number, so return the smallest index: 0.

Time and Space Complexity

  • Brute-force: For each node in initial, simulate the infection spread, which is O(n^2) per simulation, leading to O(n^3) overall.
  • Optimized (Union-Find): Building the union-find structure takes O(n^2) (since we check all pairs). Counting sizes and infections is O(n). Selecting the best node is O(k log k) for sorting (where k is length of initial). Thus, overall complexity is O(n^2).
  • Space Complexity: We use O(n) extra space for parent, size, and malware count arrays.

Summary

The problem leverages the insight that malware spreads only within connected components, and that components with a single initial infection can be completely saved by removing that node. Using Union-Find makes it efficient to identify and analyze these components. The solution is both elegant and efficient, reducing what could be a cubic brute-force problem to a quadratic one by focusing on the graph’s structure and the distribution of infections.