Facebook Pixel

924. Minimize Malware Spread

Problem Description

You have a network of n nodes represented as an n x n adjacency matrix called [graph](/problems/graph_intro). In this matrix, graph[i][j] == 1 means node i is directly connected to node j.

Initially, some nodes specified in the array initial are infected with malware. The malware spreads according to these rules:

  • When two nodes are directly connected and at least one is infected, both become infected
  • This spreading continues until no more nodes can be infected

The function M(initial) represents the total number of nodes infected after the malware finishes spreading.

Your task is to remove exactly one node from the initial array to minimize M(initial). You need to find which node to remove that results in the smallest number of total infections.

Key points to consider:

  • You must remove exactly one initially infected node
  • After removal, that node starts as uninfected (but could still get infected later if connected to other infected nodes)
  • If multiple nodes result in the same minimum infection count, return the one with the smallest index
  • The goal is to minimize the final total number of infected nodes in the network

For example, if removing node 2 from initial results in 5 total infections, and removing node 3 results in 4 total infections, you would choose to remove node 3. If both result in the same number of infections, you'd choose the one with the smaller index.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: The problem explicitly states we have a network of n nodes represented as an n x n adjacency matrix where graph[i][j] == 1 indicates nodes i and j are connected.

Is it a tree?

  • No: The graph is not necessarily a tree. It can have cycles and multiple connected components. The adjacency matrix represents a general graph structure.

Is the problem related to directed acyclic graphs?

  • No: The problem deals with an undirected graph (the adjacency matrix is symmetric) and doesn't involve directed edges or acyclic properties.

Is the problem related to shortest paths?

  • No: We're not finding shortest paths between nodes. Instead, we're tracking malware spread and trying to minimize the total number of infected nodes.

Does the problem involve connectivity?

  • Yes: The core of the problem is about connectivity. We need to understand which nodes are connected to determine how malware spreads through connected components. We need to identify connected components and count infected nodes within each component.

Disjoint Set Union

  • Yes: DSU (Union-Find) is the suggested approach for handling connectivity problems efficiently.

Conclusion: The flowchart suggests using Disjoint Set Union (DSU) for this problem. While DFS could also solve connectivity problems, DSU is more efficient for:

  1. Finding connected components in the graph
  2. Determining the size of each connected component
  3. Identifying which infected nodes belong to which component

The solution uses DSU to group nodes into connected components, then analyzes which initially infected node, when removed, would prevent the most infections by considering components with only one initially infected node.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Let's think about how malware spreads in this network. Once a node is infected, it will infect all nodes in its connected component - there's no way to stop the spread within a connected component. This is the key insight.

Now, when we remove one initially infected node, what happens? We need to consider different scenarios based on how many initially infected nodes are in each connected component:

Case 1: A connected component has 0 initially infected nodes

  • This component stays clean regardless of which node we remove
  • No impact on our decision

Case 2: A connected component has exactly 1 initially infected node

  • If we remove this node, the entire component stays clean!
  • We save all nodes in this component from infection
  • This is where we can make a difference

Case 3: A connected component has 2 or more initially infected nodes

  • Even if we remove one infected node, others will still infect the entire component
  • The whole component gets infected anyway
  • Removing any of these nodes doesn't help

This leads us to our strategy: We should focus on Case 2 components - those with exactly one initially infected node. By removing that single infected node, we prevent the entire component from being infected.

To maximize our impact, we want to:

  1. Find all connected components using Union-Find (efficient for connectivity)
  2. Count how many initially infected nodes are in each component
  3. Among components with exactly 1 infected node, choose to remove the infected node from the largest component (saves the most nodes)
  4. If there's a tie in component size, pick the node with the smallest index

If no component has exactly 1 infected node (all have 2+), then removing any node won't reduce infections - we just return the smallest indexed node as required.

The Union-Find data structure is perfect here because it efficiently:

  • Groups nodes into connected components
  • Tracks the size of each component
  • Allows us to quickly identify which component each infected node belongs to

Learn more about Depth-First Search, Breadth-First Search, Union Find and Graph patterns.

Solution Approach

Let's implement our strategy using the Union-Find data structure to efficiently manage connected components.

Step 1: Build the Union-Find Structure

First, we create a Union-Find class with path compression and union by size optimization:

  • find(x): Finds the root of node x with path compression
  • union(a, b): Merges two components, always attaching the smaller component to the larger one
  • get_size(root): Returns the size of the component with given root
class UnionFind:
    def __init__(self, n):
        self.p = list(range(n))  # parent array, initially each node is its own parent
        self.size = [1] * n      # size of each component

Step 2: Build Connected Components

We iterate through the adjacency matrix and union all connected nodes:

for i in range(n):
    for j in range(i + 1, n):
        if [graph](/problems/graph_intro)[i][j] == 1:
            uf.union(i, j)

After this step, all nodes in the same connected component will have the same root.

Step 3: Count Initially Infected Nodes per Component

We count how many initially infected nodes belong to each component:

cnt = Counter(uf.find(x) for x in initial)

Here, cnt[root] tells us how many initially infected nodes are in the component with root root.

Step 4: Find the Best Node to Remove

We examine each initially infected node and check if it's the only infected node in its component:

ans, mx = n, 0  # ans tracks the node to remove, mx tracks max nodes we can save

for x in initial:
    root = uf.find(x)
    if cnt[root] > 1:  # Multiple infected nodes in this component
        continue       # Removing x won't help
  
    # Only one infected node (x) in this component
    sz = uf.get_size(root)  # Size of this component
  
    if sz > mx or (sz == mx and x < ans):
        ans = x
        mx = sz

The logic here is:

  • If cnt[root] > 1, there are other infected nodes in this component, so removing x won't prevent the component from being infected
  • If cnt[root] == 1, removing x will save the entire component of size sz
  • We update our answer if we find a larger component to save, or same size but smaller node index

Step 5: Handle Edge Case

If no component has exactly 1 infected node (meaning ans was never updated from n), we return the minimum indexed infected node:

return min(initial) if ans == n else ans

Time Complexity: O(n²) for building the graph connections + O(m × α(n)) for Union-Find operations where m is the number of initially infected nodes and α is the inverse Ackermann function (practically constant).

Space Complexity: O(n) for the Union-Find structure and counting array.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example to illustrate how the solution works.

Example Setup:

  • Network with 6 nodes (0-5)
  • Initially infected nodes: initial = [0, 1, 4]
  • Graph connections:
    • Nodes 0, 1, 2 form a connected component (all connected to each other)
    • Nodes 3, 4 form another connected component
    • Node 5 is isolated
Adjacency Matrix:
    0 1 2 3 4 5
0 [ 0 1 1 0 0 0 ]
1 [ 1 0 1 0 0 0 ]
2 [ 1 1 0 0 0 0 ]
3 [ 0 0 0 0 1 0 ]
4 [ 0 0 0 1 0 0 ]
5 [ 0 0 0 0 0 0 ]

Step 1: Build Union-Find and identify components

After processing all edges:

  • Component A: {0, 1, 2} with root 0, size = 3
  • Component B: {3, 4} with root 3, size = 2
  • Component C: {5} with root 5, size = 1

Step 2: Count infected nodes per component

  • Component A has 2 infected nodes: 0 and 1
  • Component B has 1 infected node: 4
  • Component C has 0 infected nodes

Step 3: Evaluate each initially infected node

For node 0:

  • Root = 0, component size = 3
  • Component has 2 infected nodes (0 and 1)
  • Since count > 1, removing node 0 won't help (node 1 will still infect the component)
  • Skip this option

For node 1:

  • Root = 0, component size = 3
  • Component has 2 infected nodes (0 and 1)
  • Since count > 1, removing node 1 won't help (node 0 will still infect the component)
  • Skip this option

For node 4:

  • Root = 3, component size = 2
  • Component has exactly 1 infected node (only node 4)
  • If we remove node 4, we save the entire component of size 2
  • This is a valid candidate: saves 2 nodes

Step 4: Make the decision

  • Only node 4 can actually reduce infections
  • Removing node 4 saves 2 nodes (the entire Component B)
  • Final answer: Remove node 4

Verification of outcomes:

  • If we remove node 0: Nodes 1,2 get infected (Component A), node 4 infects node 3 (Component B) → Total: 4 infected
  • If we remove node 1: Nodes 0,2 get infected (Component A), node 4 infects node 3 (Component B) → Total: 4 infected
  • If we remove node 4: Nodes 0,1,2 get infected (Component A), nodes 3,4 stay clean → Total: 3 infected ✓

The algorithm correctly identifies that removing node 4 minimizes the total infection count.

Solution Implementation

1class UnionFind:
2    """Union-Find (Disjoint Set Union) data structure with path compression and union by size."""
3    __slots__ = "parent", "size"
4
5    def __init__(self, n: int):
6        """Initialize n disjoint sets, each containing one element."""
7        self.parent = list(range(n))  # Each node is initially its own parent
8        self.size = [1] * n  # Each set initially has size 1
9
10    def find(self, x: int) -> int:
11        """Find the root of the set containing x with path compression."""
12        if self.parent[x] != x:
13            # Path compression: make x point directly to the root
14            self.parent[x] = self.find(self.parent[x])
15        return self.parent[x]
16
17    def union(self, a: int, b: int) -> bool:
18        """
19        Union two sets containing elements a and b.
20        Returns True if sets were merged, False if already in same set.
21        """
22        root_a, root_b = self.find(a), self.find(b)
23      
24        # Already in the same set
25        if root_a == root_b:
26            return False
27      
28        # Union by size: attach smaller tree under root of larger tree
29        if self.size[root_a] > self.size[root_b]:
30            self.parent[root_b] = root_a
31            self.size[root_a] += self.size[root_b]
32        else:
33            self.parent[root_a] = root_b
34            self.size[root_b] += self.size[root_a]
35      
36        return True
37
38    def get_size(self, root: int) -> int:
39        """Get the size of the set with the given root."""
40        return self.size[root]
41
42
43from typing import List
44from collections import Counter
45
46
47class Solution:
48    def minMalwareSpread(self, graph: List[List[int]], initial: List[int]) -> int:
49        """
50        Find which infected node to remove to minimize malware spread.
51      
52        Args:
53            graph: Adjacency matrix where graph[i][j] = 1 means nodes i and j are connected
54            initial: List of initially infected nodes
55          
56        Returns:
57            The node from initial list to remove to minimize final infection count
58        """
59        n = len(graph)
60      
61        # Build connected components using Union-Find
62        uf = UnionFind(n)
63        for i in range(n):
64            for j in range(i + 1, n):
65                # If there's an edge between i and j, union them
66                if graph[i][j] == 1:
67                    uf.union(i, j)
68      
69        # Count how many infected nodes are in each connected component
70        # Key: component root, Value: count of infected nodes in that component
71        infected_count_per_component = Counter(uf.find(node) for node in initial)
72      
73        # Find the best node to remove
74        best_node_to_remove = n  # Initialize with value larger than any valid node
75        max_saved_nodes = 0
76      
77        for infected_node in initial:
78            component_root = uf.find(infected_node)
79          
80            # Skip if this component has multiple infected nodes
81            # (removing one won't save the component)
82            if infected_count_per_component[component_root] > 1:
83                continue
84          
85            # This component has only one infected node
86            # Removing it would save the entire component
87            component_size = uf.get_size(component_root)
88          
89            # Update best choice if this saves more nodes, or same but smaller index
90            if (component_size > max_saved_nodes or 
91                (component_size == max_saved_nodes and infected_node < best_node_to_remove)):
92                best_node_to_remove = infected_node
93                max_saved_nodes = component_size
94      
95        # If no single-infected component found, return smallest indexed node
96        if best_node_to_remove == n:
97            return min(initial)
98        else:
99            return best_node_to_remove
100
1/**
2 * Union-Find (Disjoint Set Union) data structure with path compression and union by size
3 */
4class UnionFind {
5    private final int[] parent;  // parent[i] stores the parent of node i
6    private final int[] size;     // size[i] stores the size of the component with root i
7  
8    /**
9     * Initialize Union-Find structure with n nodes (0 to n-1)
10     * @param n number of nodes
11     */
12    public UnionFind(int n) {
13        parent = new int[n];
14        size = new int[n];
15      
16        // Initially, each node is its own parent with size 1
17        for (int i = 0; i < n; i++) {
18            parent[i] = i;
19            size[i] = 1;
20        }
21    }
22  
23    /**
24     * Find the root of the component containing node x with path compression
25     * @param x the node to find root for
26     * @return root of the component containing x
27     */
28    public int find(int x) {
29        // Path compression: make every node on the path point directly to root
30        if (parent[x] != x) {
31            parent[x] = find(parent[x]);
32        }
33        return parent[x];
34    }
35  
36    /**
37     * Union two components containing nodes a and b using union by size
38     * @param a first node
39     * @param b second node
40     * @return true if union was performed, false if already in same component
41     */
42    public boolean union(int a, int b) {
43        int rootA = find(a);
44        int rootB = find(b);
45      
46        // Already in the same component
47        if (rootA == rootB) {
48            return false;
49        }
50      
51        // Union by size: attach smaller tree under larger tree
52        if (size[rootA] > size[rootB]) {
53            parent[rootB] = rootA;
54            size[rootA] += size[rootB];
55        } else {
56            parent[rootA] = rootB;
57            size[rootB] += size[rootA];
58        }
59      
60        return true;
61    }
62  
63    /**
64     * Get the size of the component with given root
65     * @param root root of the component
66     * @return size of the component
67     */
68    public int size(int root) {
69        return size[root];
70    }
71}
72
73/**
74 * Solution for Minimize Malware Spread problem
75 */
76class Solution {
77    /**
78     * Find which initially infected node to remove to minimize final infection spread
79     * @param graph adjacency matrix where graph[i][j] = 1 means nodes i and j are connected
80     * @param initial array of initially infected nodes
81     * @return node to remove that minimizes infection spread
82     */
83    public int minMalwareSpread(int[][] graph, int[] initial) {
84        int n = graph.length;
85      
86        // Build connected components using Union-Find
87        UnionFind unionFind = new UnionFind(n);
88        for (int i = 0; i < n; i++) {
89            for (int j = i + 1; j < n; j++) {
90                if (graph[i][j] == 1) {
91                    unionFind.union(i, j);
92                }
93            }
94        }
95      
96        // Initialize variables for finding the best node to remove
97        int nodeToRemove = n;  // Node that should be removed (initialized to invalid value)
98        int minInitialNode = n;  // Minimum node value among initially infected nodes
99        int maxComponentSize = 0;  // Maximum size of component that can be saved
100      
101        // Count how many initially infected nodes are in each component
102        int[] infectedCountPerComponent = new int[n];
103        for (int infectedNode : initial) {
104            infectedCountPerComponent[unionFind.find(infectedNode)]++;
105            minInitialNode = Math.min(minInitialNode, infectedNode);
106        }
107      
108        // Find the best node to remove
109        for (int infectedNode : initial) {
110            int componentRoot = unionFind.find(infectedNode);
111          
112            // Only consider components with exactly one infected node
113            // (removing the node will save the entire component)
114            if (infectedCountPerComponent[componentRoot] == 1) {
115                int componentSize = unionFind.size(componentRoot);
116              
117                // Choose node that saves the largest component
118                // In case of tie, choose the node with smaller index
119                if (componentSize > maxComponentSize || 
120                    (componentSize == maxComponentSize && infectedNode < nodeToRemove)) {
121                    nodeToRemove = infectedNode;
122                    maxComponentSize = componentSize;
123                }
124            }
125        }
126      
127        // If no component can be saved, return the minimum infected node
128        return nodeToRemove == n ? minInitialNode : nodeToRemove;
129    }
130}
131
1class UnionFind {
2public:
3    // Initialize Union-Find structure with n elements
4    UnionFind(int n) {
5        parent = vector<int>(n);
6        componentSize = vector<int>(n, 1);
7        // Initially, each element is its own parent (self-loop)
8        iota(parent.begin(), parent.end(), 0);
9    }
10
11    // Unite two elements into the same component
12    // Returns true if they were in different components, false if already united
13    bool unite(int a, int b) {
14        int rootA = find(a);
15        int rootB = find(b);
16      
17        // Already in the same component
18        if (rootA == rootB) {
19            return false;
20        }
21      
22        // Union by size: attach smaller tree under root of larger tree
23        if (componentSize[rootA] > componentSize[rootB]) {
24            parent[rootB] = rootA;
25            componentSize[rootA] += componentSize[rootB];
26        } else {
27            parent[rootA] = rootB;
28            componentSize[rootB] += componentSize[rootA];
29        }
30        return true;
31    }
32
33    // Find the root of the component containing element x
34    // Uses path compression for optimization
35    int find(int x) {
36        if (parent[x] != x) {
37            parent[x] = find(parent[x]);  // Path compression
38        }
39        return parent[x];
40    }
41
42    // Get the size of the component with given root
43    int getSize(int root) {
44        return componentSize[root];
45    }
46
47private:
48    vector<int> parent;        // Parent pointers for each element
49    vector<int> componentSize; // Size of each component (valid only for roots)
50};
51
52class Solution {
53public:
54    int minMalwareSpread(vector<vector<int>>& graph, vector<int>& initial) {
55        int n = graph.size();
56      
57        // Build connected components using Union-Find
58        UnionFind uf(n);
59        for (int i = 0; i < n; ++i) {
60            for (int j = i + 1; j < n; ++j) {
61                if (graph[i][j]) {
62                    uf.unite(i, j);
63                }
64            }
65        }
66      
67        // Initialize result variables
68        int result = n;  // Store the node to remove (initialized to n as sentinel)
69        int maxSaved = 0;  // Maximum number of nodes that can be saved
70      
71        // Count initially infected nodes in each component
72        vector<int> infectedCount(n, 0);
73        for (int node : initial) {
74            int root = uf.find(node);
75            ++infectedCount[root];
76        }
77      
78        // For each initially infected node, check if removing it helps
79        for (int node : initial) {
80            int root = uf.find(node);
81          
82            // Only consider components with exactly one infected node
83            // (removing the only infected node saves the entire component)
84            if (infectedCount[root] == 1) {
85                int componentSize = uf.getSize(root);
86              
87                // Update result if this saves more nodes, or same but smaller index
88                if (componentSize > maxSaved || 
89                    (componentSize == maxSaved && result > node)) {
90                    result = node;
91                    maxSaved = componentSize;
92                }
93            }
94        }
95      
96        // If no node removal helps, return the smallest indexed infected node
97        return result == n ? *min_element(initial.begin(), initial.end()) : result;
98    }
99};
100
1// Parent array for Union-Find structure
2let parent: number[];
3// Size array to track component sizes
4let componentSize: number[];
5
6/**
7 * Initialize Union-Find data structure
8 * @param n - Number of nodes
9 */
10function initializeUnionFind(n: number): void {
11    // Each node is initially its own parent
12    parent = Array(n).fill(0).map((_, index) => index);
13    // Each component initially has size 1
14    componentSize = Array(n).fill(1);
15}
16
17/**
18 * Find the root parent of a node with path compression
19 * @param x - Node to find root for
20 * @returns Root parent of the node
21 */
22function find(x: number): number {
23    // Path compression: make all nodes point directly to root
24    if (parent[x] !== x) {
25        parent[x] = find(parent[x]);
26    }
27    return parent[x];
28}
29
30/**
31 * Union two nodes into the same component using union by size
32 * @param a - First node
33 * @param b - Second node
34 * @returns True if union was performed, false if already in same component
35 */
36function union(a: number, b: number): boolean {
37    // Find root parents of both nodes
38    const rootA = find(a);
39    const rootB = find(b);
40  
41    // Already in same component
42    if (rootA === rootB) {
43        return false;
44    }
45  
46    // Union by size: attach smaller tree under larger tree
47    if (componentSize[rootA] > componentSize[rootB]) {
48        parent[rootB] = rootA;
49        componentSize[rootA] += componentSize[rootB];
50    } else {
51        parent[rootA] = rootB;
52        componentSize[rootB] += componentSize[rootA];
53    }
54  
55    return true;
56}
57
58/**
59 * Get the size of the component containing the given root
60 * @param root - Root node of the component
61 * @returns Size of the component
62 */
63function getSize(root: number): number {
64    return componentSize[root];
65}
66
67/**
68 * Find the node to remove that minimizes malware spread
69 * @param graph - Adjacency matrix representing connections
70 * @param initial - Array of initially infected nodes
71 * @returns Node to remove that minimizes spread
72 */
73function minMalwareSpread(graph: number[][], initial: number[]): number {
74    const nodeCount = graph.length;
75  
76    // Initialize Union-Find structure
77    initializeUnionFind(nodeCount);
78  
79    // Build connected components based on graph edges
80    for (let i = 0; i < nodeCount; i++) {
81        for (let j = i + 1; j < nodeCount; j++) {
82            // If there's an edge between i and j, union them
83            if (graph[i][j]) {
84                union(i, j);
85            }
86        }
87    }
88  
89    // Initialize result and maximum component size saved
90    let result = nodeCount;
91    let maxSizeSaved = 0;
92  
93    // Count infected nodes in each component
94    const infectedCountPerComponent: number[] = Array(nodeCount).fill(0);
95    for (const infectedNode of initial) {
96        const componentRoot = find(infectedNode);
97        infectedCountPerComponent[componentRoot]++;
98    }
99  
100    // Check each initially infected node
101    for (const candidateNode of initial) {
102        const componentRoot = find(candidateNode);
103      
104        // Only consider nodes that are the sole infected in their component
105        if (infectedCountPerComponent[componentRoot] === 1) {
106            const currentComponentSize = getSize(componentRoot);
107          
108            // Update result if this saves more nodes or breaks tie with smaller index
109            if (currentComponentSize > maxSizeSaved || 
110                (currentComponentSize === maxSizeSaved && candidateNode < result)) {
111                result = candidateNode;
112                maxSizeSaved = currentComponentSize;
113            }
114        }
115    }
116  
117    // If no single infected component found, return minimum infected node
118    return result === nodeCount ? Math.min(...initial) : result;
119}
120

Time and Space Complexity

Time Complexity: O(n² × α(n))

The time complexity is dominated by the graph traversal and union-find operations:

  • The nested loops iterate through all pairs of nodes: O(n²) iterations
  • For each edge where graph[i][j] = 1, we perform a union operation
  • Each union operation involves two find operations, which have amortized complexity of O(α(n)) due to path compression
  • Finding roots for all initial nodes: O(|initial| × α(n)) where |initial| ≤ n
  • The final loop through initial nodes: O(|initial|) operations

Therefore, the overall time complexity is O(n² × α(n)), where α(n) is the inverse Ackermann function, which grows extremely slowly and is effectively constant for all practical values of n.

Space Complexity: O(n)

The space complexity consists of:

  • Union-Find data structure stores two arrays p and size, each of size n: O(n)
  • Counter for storing root counts: O(|initial|) which is at most O(n)
  • Recursive call stack for find operation with path compression: O(log n) in worst case before compression
  • Other variables use constant space: O(1)

The total space complexity is O(n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Misunderstanding What "Removing" a Node Means

The Problem: Many developers incorrectly interpret "removing a node" as deleting it entirely from the graph, including all its edges. This leads to complicated graph reconstruction logic and incorrect results.

What Actually Happens: When we "remove" a node from the initial array, we're only changing its initial infection status from infected to clean. The node and its connections remain in the graph. It can still get infected later if connected to other infected nodes.

Incorrect Approach:

# WRONG: Trying to delete the node from the graph
def remove_node(graph, node):
    n = len(graph)
    for i in range(n):
        graph[node][i] = 0
        graph[i][node] = 0

Correct Understanding: The node stays in the graph with all its connections intact. We're just choosing not to start with it as infected.

Pitfall 2: Incorrectly Handling Components with Multiple Infected Nodes

The Problem: When a connected component has multiple initially infected nodes, some might think removing one of them reduces the infection count by the component size.

Why It's Wrong: If a component has nodes A, B, C, D where A and B are initially infected:

  • Even if we remove A from initial, B is still infected
  • B will spread to all nodes in the component including A
  • The entire component still gets infected

Incorrect Logic:

# WRONG: Counting saved nodes even when multiple infected in component
for node in initial:
    component_root = uf.find(node)
    saved_nodes = uf.get_size(component_root)  # Wrong! Doesn't check if others are infected
    # ... update best choice

Correct Logic:

# RIGHT: Only count savings when exactly one infected node in component
if infected_count_per_component[component_root] > 1:
    continue  # Can't save this component

Pitfall 3: Not Handling the "All Components Have Multiple Infections" Case

The Problem: If every connected component containing initially infected nodes has 2+ infected nodes, removing any single node won't prevent any component from being infected. The code might return an invalid result or crash.

Example Scenario:

graph = [[1,1,0], [1,1,0], [0,0,1]]
initial = [0, 1]  # Both in same component
# Component {0,1} has 2 infected nodes
# Component {2} has 0 infected nodes
# No matter which we remove, component {0,1} stays infected

Solution: Always have a fallback to return the minimum indexed node when no component can be saved:

if best_node_to_remove == n:  # Never updated, no single-infected component found
    return min(initial)  # Return smallest index as tiebreaker

Pitfall 4: Incorrect Tiebreaker Logic

The Problem: When multiple nodes would save the same number of nodes, the problem asks for the smallest indexed node. Some implementations might return the first one found or the last one found instead.

Wrong Implementation:

# WRONG: Returns last one found when tied
if component_size >= max_saved_nodes:  # Should be > not >=
    best_node_to_remove = infected_node

Correct Implementation:

# RIGHT: Properly handles ties by choosing smaller index
if (component_size > max_saved_nodes or 
    (component_size == max_saved_nodes and infected_node < best_node_to_remove)):
    best_node_to_remove = infected_node
    max_saved_nodes = component_size

Complete Solution to Avoid These Pitfalls:

The provided solution correctly handles all these cases by:

  1. Using Union-Find to identify connected components without modifying the graph
  2. Counting infected nodes per component to identify which can be saved
  3. Only considering components with exactly one infected node
  4. Having a fallback for when no component can be saved
  5. Properly handling the tiebreaker by checking node indices

The key insight is that we can only prevent a component's infection if it has exactly one initially infected node, and we remove that specific node from the initial set.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What is the best way of checking if an element exists in an unsorted array once in terms of time complexity? Select the best that applies.


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More