Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

924. Minimize Malware Spread - Leetcode Solution

Problem Description

The "Minimize Malware Spread" problem on LeetCode presents you with a network of n nodes, represented as an n x n adjacency matrix called graph. Each node is either clean or initially infected by malware. The list initial contains the indices of infected nodes. Malware spreads from infected nodes to directly connected nodes (in the same connected component).

You can remove exactly one node from initial to minimize the total number of infected nodes after the spread stops. If there are multiple optimal nodes to remove, return the one with the smallest index.

  • Each node can only be removed once.
  • The initial list may be unordered.
  • All nodes in initial are distinct.
  • Return the index of the node to remove.

Constraints:
1 <= n <= 300
graph.length == n
graph[i][j] is 0 or 1
initial.length >= 1

Thought Process

At first glance, a brute-force approach might be to simulate removing each node in initial and count the resulting spread. However, this would be inefficient for large networks.

To optimize, we recognize that malware only spreads within connected components. If a component contains more than one initially infected node, removing just one won't stop the spread in that component. But if a component contains exactly one infected node, removing it can prevent the entire component from being infected.

So, the key insight is to:

  • Identify all connected components in the network.
  • Count how many initial infections are in each component.
  • For each node in initial, if it is the only infected node in its component, removing it will save all other nodes in the component from infection.
  • Among all such nodes, remove the one that saves the most nodes (break ties by index).
This approach dramatically reduces unnecessary simulation and focuses on the structural properties of the network.

Solution Approach

  • Step 1: Find Connected Components
    • Use DFS or Union-Find to label each node with its component id.
  • Step 2: Count Nodes per Component
    • For each component, count how many nodes it contains.
  • Step 3: Count Initial Infections per Component
    • For each component, count how many nodes in initial are present.
  • Step 4: Evaluate Each Initial Node
    • For each node in initial, if it is the only infected node in its component, removing it will save all the other nodes in that component.
    • Track the node that saves the most nodes (or the smallest index in case of a tie).
  • Step 5: Return the Answer
    • If no node can save any nodes, return the node with the smallest index from initial.

This method leverages a hash map for quick component lookups, and sorts initial to handle tie-breaking efficiently.

Example Walkthrough

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

  • The network has two components: {0,1} and {2}.
  • Component {0,1} contains both infected nodes (0 and 1). Component {2} is clean.
  • Removing either 0 or 1 does not prevent the other from infecting the component. So, after the spread, both 0 and 1 will be infected.
  • Since both choices result in the same number of infections, return the node with the smallest index: 0.

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

  • Each node is its own component.
  • Removing 0 will save node 0's component from infection (size 1).
  • Removing 2 will save node 2's component (also size 1).
  • Both save the same, so return the node with the smallest index: 0.

Code Implementation

class Solution:
    def minMalwareSpread(self, graph, initial):
        n = len(graph)
        colors = [-1] * n
        def dfs(node, color):
            for nei in range(n):
                if graph[node][nei] == 1 and colors[nei] == -1:
                    colors[nei] = color
                    dfs(nei, color)
        color = 0
        for node in range(n):
            if colors[node] == -1:
                colors[node] = color
                dfs(node, color)
                color += 1
        # Count size of each color/component
        size = [0] * color
        for c in colors:
            size[c] += 1
        # Count number of initial nodes in each component
        initial_in_color = [0] * color
        for node in initial:
            initial_in_color[colors[node]] += 1
        # Try removing each node in initial
        result = float('inf')
        max_saved = -1
        for node in sorted(initial):
            c = colors[node]
            if initial_in_color[c] == 1:
                if size[c] > max_saved:
                    max_saved = size[c]
                    result = node
                elif size[c] == max_saved and node < result:
                    result = node
        if result == float('inf'):
            result = min(initial)
        return result
      
class Solution {
public:
    int minMalwareSpread(vector<vector<int>>& graph, vector<int>& initial) {
        int n = graph.size();
        vector<int> colors(n, -1);
        int color = 0;
        function<void(int, int)> dfs = [&](int node, int c) {
            for (int nei = 0; nei < n; ++nei) {
                if (graph[node][nei] && colors[nei] == -1) {
                    colors[nei] = c;
                    dfs(nei, c);
                }
            }
        };
        for (int i = 0; i < n; ++i) {
            if (colors[i] == -1) {
                colors[i] = color;
                dfs(i, color++);
            }
        }
        vector<int> size(color, 0), initial_in_color(color, 0);
        for (int i = 0; i < n; ++i) size[colors[i]]++;
        for (int node : initial) initial_in_color[colors[node]]++;
        sort(initial.begin(), initial.end());
        int result = INT_MAX, max_saved = -1;
        for (int node : initial) {
            int c = colors[node];
            if (initial_in_color[c] == 1) {
                if (size[c] > max_saved) {
                    max_saved = size[c];
                    result = node;
                } else if (size[c] == max_saved && node < result) {
                    result = node;
                }
            }
        }
        if (result == INT_MAX) result = *min_element(initial.begin(), initial.end());
        return result;
    }
};
      
class Solution {
    public int minMalwareSpread(int[][] graph, int[] initial) {
        int n = graph.length;
        int[] colors = new int[n];
        Arrays.fill(colors, -1);
        int color = 0;
        for (int i = 0; i < n; ++i) {
            if (colors[i] == -1) {
                dfs(graph, colors, i, color++);
            }
        }
        int[] size = new int[color];
        for (int c : colors) size[c]++;
        int[] initialInColor = new int[color];
        for (int node : initial) initialInColor[colors[node]]++;
        Arrays.sort(initial);
        int result = Integer.MAX_VALUE, maxSaved = -1;
        for (int node : initial) {
            int c = colors[node];
            if (initialInColor[c] == 1) {
                if (size[c] > maxSaved) {
                    maxSaved = size[c];
                    result = node;
                } else if (size[c] == maxSaved && node < result) {
                    result = node;
                }
            }
        }
        if (result == Integer.MAX_VALUE) result = initial[0];
        return result;
    }
    private void dfs(int[][] graph, int[] colors, int node, int color) {
        colors[node] = color;
        for (int nei = 0; nei < graph.length; ++nei) {
            if (graph[node][nei] == 1 && colors[nei] == -1) {
                dfs(graph, colors, nei, color);
            }
        }
    }
}
      
var minMalwareSpread = function(graph, initial) {
    const n = graph.length;
    const colors = Array(n).fill(-1);
    let color = 0;
    function dfs(node, c) {
        for (let nei = 0; nei < n; ++nei) {
            if (graph[node][nei] && colors[nei] === -1) {
                colors[nei] = c;
                dfs(nei, c);
            }
        }
    }
    for (let i = 0; i < n; ++i) {
        if (colors[i] === -1) {
            colors[i] = color;
            dfs(i, color++);
        }
    }
    const size = Array(color).fill(0);
    for (const c of colors) size[c]++;
    const initialInColor = Array(color).fill(0);
    for (const node of initial) initialInColor[colors[node]]++;
    initial.sort((a, b) => a - b);
    let result = Infinity, maxSaved = -1;
    for (const node of initial) {
        const c = colors[node];
        if (initialInColor[c] === 1) {
            if (size[c] > maxSaved) {
                maxSaved = size[c];
                result = node;
            } else if (size[c] === maxSaved && node < result) {
                result = node;
            }
        }
    }
    if (result === Infinity) result = Math.min(...initial);
    return result;
};
      

Time and Space Complexity

  • Brute-force approach: For each node in initial, simulate the spread (BFS/DFS) after removing it. This can be O(n^3) in the worst case due to repeated graph traversals.
  • Optimized approach:
    • Finding components: O(n^2) (since the graph is dense, visiting all edges)
    • Counting and processing: O(n)
    • Overall: O(n^2) time and O(n) space for color/component labeling and bookkeeping.
  • Why? The graph is represented as an adjacency matrix, so visiting all possible edges is O(n^2). All other steps are linear in n.

Summary

The "Minimize Malware Spread" problem is elegantly solved by leveraging the structure of connected components. By focusing on components with exactly one initial infection, we can efficiently determine which node's removal will maximally reduce the spread. This avoids brute-force simulation and instead uses graph traversal and counting, resulting in a solution that's both efficient and easy to understand.