Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

749. Contain Virus - Leetcode Solution

Problem Description

You are given a 2D grid representing a laboratory where some cells are infected by a virus, and others are uninfected. The grid is a matrix of integers, where:

  • grid[i][j] == 1 means the cell at row i and column j is infected.
  • grid[i][j] == 0 means the cell is uninfected.
  • grid[i][j] == -1 means the cell is contained by a wall and cannot be infected anymore.
Every day, the virus spreads from each infected cell to its adjacent (up, down, left, right) uninfected cells, unless blocked by a wall. However, each day, you can build walls around one of the infected regions (a group of connected infected cells) to contain it. The goal is to minimize the total number of walls built until the virus cannot spread anymore.

Key constraints:
  • Each day, you must choose the region that would infect the most uninfected cells if not contained, and build walls around it.
  • After building walls, the virus in other regions spreads normally.
  • Do not reuse elements; once a cell is contained, it stays contained.
  • Return the total number of walls built.

Thought Process

The problem is about simulating the spread of a virus in a grid, with the added complexity that we can contain one region per day. At first glance, a brute-force approach might be to simulate all possible wall placements, but this would be too slow for large grids.

Instead, we should focus on the greedy strategy described in the prompt: always contain the most "dangerous" region (the one that would infect the most new cells). This means:

  • Identifying all infected regions each day.
  • For each region, determining how many uninfected cells it would infect next.
  • Building walls around the region with the highest potential spread.
  • Letting the other regions infect their neighbors.
The main challenge is efficiently identifying regions and their threats, updating the grid, and keeping track of walls built.

Solution Approach

We can break down the solution into the following steps:

  1. Identify Infected Regions:
    • Use BFS or DFS to find all groups of connected infected cells (regions).
    • For each region, record:
      • The set of infected cells in the region.
      • The set of adjacent uninfected cells that could be infected next.
      • The number of walls needed to contain the region (counting each boundary between an infected and uninfected cell).
  2. Choose the Most Dangerous Region:
    • Pick the region with the largest number of adjacent uninfected cells (i.e., would infect the most cells next).
  3. Contain the Region:
    • Set all cells in that region to -1 (contained).
    • Add the required number of walls to the total.
  4. Spread the Virus:
    • For all other regions, infect their adjacent uninfected cells (set them to 1).
  5. Repeat Until Done:
    • Continue the process until no more cells can be infected (no uncontained infected regions).
We use sets to efficiently track which cells each region can infect and how many walls are needed. BFS is preferred for region discovery because it avoids recursion stack limits.

Example Walkthrough

Sample Input:
grid = [ [0,1,0,0,0,0,0,1], [0,1,0,0,0,0,0,1], [0,0,0,0,0,0,0,1], [0,0,0,0,0,0,0,0] ]

Step 1: Identify all infected regions:

  • Region 1: cells at (0,1), (1,1)
  • Region 2: cells at (0,7), (1,7), (2,7)
Step 2: For each region, count how many uninfected cells they can infect next:
  • Region 1: can infect 3 cells
  • Region 2: can infect 4 cells
Step 3: Contain Region 2 (most dangerous), build walls (6 walls needed).
Step 4: Spread the virus from Region 1 (infects its adjacent uninfected cells).
Step 5: Repeat: identify new regions, choose the most dangerous, contain, and spread.
This process continues until no more uninfected cells can be infected.

Time and Space Complexity

Brute-force Approach:

  • Would try all possible wall placements, leading to exponential time complexity: O(2^(m*n)), not feasible.
Optimized Approach:
  • Each day, we scan the grid to identify regions: O(m*n).
  • For each region, BFS/DFS visits each cell at most once per day.
  • In the worst case (every cell is infected), the process repeats O(m*n) times, but usually much fewer.
  • Total Time Complexity: O((m*n)^2) in the worst case, but typically much better in practice.
  • Space Complexity: O(m*n) for storing region info and auxiliary data structures.

Summary

The key insight is to use a greedy approach: always contain the region that threatens to infect the most new cells. By systematically identifying regions, tracking their potential spread, and simulating the virus, we efficiently minimize the number of walls needed. The use of BFS/DFS, sets for tracking, and careful simulation makes the solution both clear and efficient.

Code Implementation

class Solution:
    def containVirus(self, grid):
        from collections import deque, defaultdict
        m, n = len(grid), len(grid[0])
        total_walls = 0

        def neighbors(r, c):
            for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
                nr, nc = r+dr, c+dc
                if 0 <= nr < m and 0 <= nc < n:
                    yield nr, nc

        while True:
            visited = [[False]*n for _ in range(m)]
            regions = []
            frontiers = []
            walls_needed = []

            for r in range(m):
                for c in range(n):
                    if grid[r][c] == 1 and not visited[r][c]:
                        region = []
                        frontier = set()
                        walls = 0
                        queue = deque()
                        queue.append((r, c))
                        visited[r][c] = True
                        while queue:
                            x, y = queue.popleft()
                            region.append((x, y))
                            for nx, ny in neighbors(x, y):
                                if grid[nx][ny] == 0:
                                    frontier.add((nx, ny))
                                    walls += 1
                                elif grid[nx][ny] == 1 and not visited[nx][ny]:
                                    visited[nx][ny] = True
                                    queue.append((nx, ny))
                        regions.append(region)
                        frontiers.append(frontier)
                        walls_needed.append(walls)

            if not regions:
                break

            # Find the region with the max frontier
            max_idx = -1
            max_frontier = 0
            for i, frontier in enumerate(frontiers):
                if len(frontier) > max_frontier:
                    max_frontier = len(frontier)
                    max_idx = i

            if max_frontier == 0:
                break

            # Build walls for the most threatening region
            total_walls += walls_needed[max_idx]
            for x, y in regions[max_idx]:
                grid[x][y] = -1

            # Spread virus for other regions
            for i, region in enumerate(regions):
                if i == max_idx:
                    continue
                for x, y in frontiers[i]:
                    grid[x][y] = 1

        return total_walls
      
class Solution {
public:
    int containVirus(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        int totalWalls = 0;
        vector<pair<int,int>> dirs = {{-1,0},{1,0},{0,-1},{0,1}};
        while (true) {
            vector<vector<bool>> visited(m, vector<bool>(n, false));
            vector<vector<pair<int,int>>> regions;
            vector<set<pair<int,int>>> frontiers;
            vector<int> wallsNeeded;

            for (int r = 0; r < m; ++r) {
                for (int c = 0; c < n; ++c) {
                    if (grid[r][c] == 1 && !visited[r][c]) {
                        vector<pair<int,int>> region;
                        set<pair<int,int>> frontier;
                        int walls = 0;
                        queue<pair<int,int>> q;
                        q.push({r, c});
                        visited[r][c] = true;
                        while (!q.empty()) {
                            auto [x, y] = q.front(); q.pop();
                            region.push_back({x, y});
                            for (auto& d : dirs) {
                                int nx = x + d.first, ny = y + d.second;
                                if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
                                    if (grid[nx][ny] == 0) {
                                        frontier.insert({nx, ny});
                                        walls++;
                                    } else if (grid[nx][ny] == 1 && !visited[nx][ny]) {
                                        visited[nx][ny] = true;
                                        q.push({nx, ny});
                                    }
                                }
                            }
                        }
                        regions.push_back(region);
                        frontiers.push_back(frontier);
                        wallsNeeded.push_back(walls);
                    }
                }
            }

            if (regions.empty()) break;

            int maxIdx = -1, maxFrontier = 0;
            for (int i = 0; i < frontiers.size(); ++i) {
                if (frontiers[i].size() > maxFrontier) {
                    maxFrontier = frontiers[i].size();
                    maxIdx = i;
                }
            }
            if (maxFrontier == 0) break;

            totalWalls += wallsNeeded[maxIdx];
            for (auto& cell : regions[maxIdx]) {
                grid[cell.first][cell.second] = -1;
            }

            for (int i = 0; i < regions.size(); ++i) {
                if (i == maxIdx) continue;
                for (auto& cell : frontiers[i]) {
                    grid[cell.first][cell.second] = 1;
                }
            }
        }
        return totalWalls;
    }
};
      
class Solution {
    public int containVirus(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        int totalWalls = 0;
        int[][] dirs = {{-1,0},{1,0},{0,-1},{0,1}};
        while (true) {
            boolean[][] visited = new boolean[m][n];
            List<List<int[]>> regions = new ArrayList<>();
            List<Set<String>> frontiers = new ArrayList<>();
            List<Integer> wallsNeeded = new ArrayList<>();

            for (int r = 0; r < m; r++) {
                for (int c = 0; c < n; c++) {
                    if (grid[r][c] == 1 && !visited[r][c]) {
                        List<int[]> region = new ArrayList<>();
                        Set<String> frontier = new HashSet<>();
                        int walls = 0;
                        Queue<int[]> q = new LinkedList<>();
                        q.offer(new int[]{r, c});
                        visited[r][c] = true;
                        while (!q.isEmpty()) {
                            int[] cell = q.poll();
                            region.add(cell);
                            for (int[] d : dirs) {
                                int nx = cell[0] + d[0], ny = cell[1] + d[1];
                                if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
                                    if (grid[nx][ny] == 0) {
                                        frontier.add(nx + "," + ny);
                                        walls++;
                                    } else if (grid[nx][ny] == 1 && !visited[nx][ny]) {
                                        visited[nx][ny] = true;
                                        q.offer(new int[]{nx, ny});
                                    }
                                }
                            }
                        }
                        regions.add(region);
                        frontiers.add(frontier);
                        wallsNeeded.add(walls);
                    }
                }
            }

            if (regions.isEmpty()) break;

            int maxIdx = -1, maxFrontier = 0;
            for (int i = 0; i < frontiers.size(); i++) {
                if (frontiers.get(i).size() > maxFrontier) {
                    maxFrontier = frontiers.get(i).size();
                    maxIdx = i;
                }
            }
            if (maxFrontier == 0) break;

            totalWalls += wallsNeeded.get(maxIdx);
            for (int[] cell : regions.get(maxIdx)) {
                grid[cell[0]][cell[1]] = -1;
            }

            for (int i = 0; i < regions.size(); i++) {
                if (i == maxIdx) continue;
                for (String s : frontiers.get(i)) {
                    String[] parts = s.split(",");
                    int x = Integer.parseInt(parts[0]);
                    int y = Integer.parseInt(parts[1]);
                    grid[x][y] = 1;
                }
            }
        }
        return totalWalls;
    }
}
      
var containVirus = function(grid) {
    let m = grid.length, n = grid[0].length;
    let dirs = [[-1,0],[1,0],[0,-1],[0,1]];
    let totalWalls = 0;

    while (true) {
        let visited = Array.from({length: m}, () => Array(n).fill(false));
        let regions = [], frontiers = [], wallsNeeded = [];

        for (let r = 0; r < m; r++) {
            for (let c = 0; c < n; c++) {
                if (grid[r][c] === 1 && !visited[r][c]) {
                    let region = [], frontier = new Set(), walls = 0;
                    let queue = [[r, c]];
                    visited[r][c] = true;
                    while (queue.length) {
                        let [x, y] = queue.shift();
                        region.push([x, y]);
                        for (let [dx, dy] of dirs) {
                            let nx = x + dx, ny = y + dy;
                            if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
                                if (grid[nx][ny] === 0) {
                                    frontier.add(nx + ',' + ny);
                                    walls++;
                                } else if (grid[nx][ny] === 1 && !visited[nx][ny]) {
                                    visited[nx][ny] = true;
                                    queue.push([nx, ny]);
                                }
                            }
                        }
                    }
                    regions.push(region);
                    frontiers.push(frontier);
                    wallsNeeded.push(walls);
                }
            }
        }

        if (regions.length === 0) break;

        let maxIdx = -1, maxFrontier = 0;
        for (let i = 0; i < frontiers.length; i++) {
            if (frontiers[i].size > maxFrontier) {
                maxFrontier = frontiers[i].size;
                maxIdx = i;
            }
        }
        if (maxFrontier === 0) break;

        totalWalls += wallsNeeded[maxIdx];
        for (let [x, y] of regions[maxIdx]) {
            grid[x][y] = -1;
        }

        for (let i = 0; i < regions.length; i++) {
            if (i === maxIdx) continue;
            for (let s of frontiers[i]) {
                let [nx, ny] = s.split(',').map(Number);
                grid[nx][ny] = 1;
            }
        }
    }
    return totalWalls;
};