文章目录
题目
标题和出处
标题:隔离病毒
出处:749. 隔离病毒
难度
8 级
题目描述
要求
病毒在快速扩散,你的任务是通过安装防火墙隔离病毒。
世界建模成 m × n \texttt{m} \times \texttt{n} m×n 的二维网格 isInfected \texttt{isInfected} isInfected 组成, isInfectedij = 0 \texttt{isInfectedij} = \texttt{0} isInfectedij=0 表示未感染的单元格, isInfectedij = 1 \texttt{isInfectedij} = \texttt{1} isInfectedij=1 表示已感染病毒的单元格。可以在四个方向相邻的任意两个单元格之间的共享边界上安装一个防火墙(并且只有一个防火墙)。
每天晚上,病毒会从被感染单元格向四个方向相邻的未感染单元格扩散,除非被防火墙隔离。由于资源有限,每天只能安装一系列防火墙来隔离其中一个被病毒感染的区域(由连续的被感染单元格组成的一片区域),该感染区域威胁的未感染单元格最多。保证同一天晚上威胁未感染单元格最多的区域是唯一的。
返回隔离所有被感染区域的防火墙个数。如果整个世界将全部被感染,则返回安装的防火墙个数。
示例
示例 1:

输入: isInfected = \[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] \texttt{isInfected = \[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]} isInfected = \[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]
输出: 10 \texttt{10} 10
解释:一共有两块被病毒感染的区域。
第一天,添加 5 \texttt{5} 5 个墙隔离左侧的病毒区域。病毒传播后的状态是:

第二天,添加 5 \texttt{5} 5 个墙隔离右侧的病毒区域。此时病毒已经被完全控制住了。

示例 2:

输入: isInfected = \[1,1,1,1,0,1,1,1,1] \texttt{isInfected = \[1,1,1,1,0,1,1,1,1]} isInfected = \[1,1,1,1,0,1,1,1,1]
输出: 4 \texttt{4} 4
解释:虽然只保存了一个小区域,但需要添加 4 \texttt{4} 4 个墙。注意,防火墙只建立在两个不同单元格的共享边界上。
示例 3:
输入: isInfected = \[1,1,1,0,0,0,0,0,0,1,0,1,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0] \texttt{isInfected = \[1,1,1,0,0,0,0,0,0,1,0,1,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0]} isInfected = \[1,1,1,0,0,0,0,0,0,1,0,1,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0]
输出: 13 \texttt{13} 13
解释:隔离左边病毒区域只需要 2 \texttt{2} 2 个防火墙。
数据范围
- m = isInfected.length \texttt{m} = \texttt{isInfected.length} m=isInfected.length
- n = isInfectedi.length \texttt{n} = \texttt{isInfectedi.length} n=isInfectedi.length
- 1 ≤ m, n ≤ 50 \texttt{1} \le \texttt{m, n} \le \texttt{50} 1≤m, n≤50
- isInfectedij \texttt{isInfectedij} isInfectedij 是 0 \texttt{0} 0 或 1 \texttt{1} 1
- 在整个描述的过程中,总有一个相邻的病毒区域,该区域将在下一轮严格地感染更多未受污染的单元格
解法一
思路和算法
为了计算安装的防火墙个数,需要根据题意模拟。每一轮操作,首先找到威胁的未感染单元格最多的感染区域,安装防火墙将该区域隔离,然后将其他感染区域扩散。当没有更多的单元格被感染时,即可得到安装的防火墙个数。
可以使用广度优先搜索遍历每个感染区域,对于每个感染区域,计算该感染区域威胁的未感染单元格数目以及隔离该感染区域需要安装的防火墙个数。由于可能存在同一个感染区域的多个被感染单元格威胁同一个未感染单元格的情况,因此威胁的未感染单元格数目和需要安装的防火墙个数不一定相同,需要分别计算。
为了避免重复计算未感染单元格数目,需要使用哈希集合存储被威胁的未感染单元格。遍历过程中,如果一个被感染单元格的相邻单元格是未感染单元格,则将该未感染单元格加入哈希集合,将防火墙个数加 1 1 1。
遍历整个网格之后,即可得到威胁的未感染单元格数目的最大值,以及需要安装的防火墙个数和该感染区域的位置,执行如下操作,模拟安装防火墙将该感染区域隔离以及将其他感染区域扩散。
-
安装防火墙将该感染区域隔离之后,该感染区域不会继续扩散,为了加以区分,将被隔离的感染区域中的值改成 − 1 -1 −1。使用广度优先搜索遍历该感染区域并更新区域中的值。
-
遍历整个网格,记录与至少一个 1 1 1 相邻的值为 0 0 0 的单元格,遍历结束之后将这些单元格的值改成 1 1 1。如果在遍历的同时更改单元格的值,则前面更改的值会影响后面的判断,因此必须在遍历结束之后统一更改单元格的值。
重复上述操作,直到没有更多的未感染单元格被威胁时,操作结束,返回安装的防火墙个数。
代码
java
class Solution {
static int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int m, n;
int[][] isInfected;
public int containVirus(int[][] isInfected) {
int totalWalls = 0;
this.m = isInfected.length;
this.n = isInfected[0].length;
this.isInfected = isInfected;
boolean flag = true;
while (flag) {
int maxThreats = 0;
int currWalls = 0;
int startRow = -1, startCol = -1;
boolean[][] visited = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (isInfected[i][j] == 0) {
} else if (isInfected[i][j] == 1 && !visited[i][j]) {
int[] threatsWalls = getThreatsWalls(i, j, visited);
int threats = threatsWalls[0], walls = threatsWalls[1];
if (threats > maxThreats) {
maxThreats = threats;
currWalls = walls;
startRow = i;
startCol = j;
}
}
}
}
flag = maxThreats > 0;
if (flag) {
totalWalls += currWalls;
block(startRow, startCol);
update();
}
}
return totalWalls;
}
public int[] getThreatsWalls(int startRow, int startCol, boolean[][] visited) {
Set<Integer> threats = new HashSet<Integer>();
int walls = 0;
visited[startRow][startCol] = true;
Queue<int[]> queue = new ArrayDeque<int[]>();
queue.offer(new int[]{startRow, startCol});
while (!queue.isEmpty()) {
int[] cell = queue.poll();
int row = cell[0], col = cell[1];
for (int[] dir : dirs) {
int newRow = row + dir[0], newCol = col + dir[1];
if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n) {
if (isInfected[newRow][newCol] == 1 && !visited[newRow][newCol]) {
visited[newRow][newCol] = true;
queue.offer(new int[]{newRow, newCol});
} else if (isInfected[newRow][newCol] == 0) {
threats.add(newRow * n + newCol);
walls++;
}
}
}
}
return new int[]{threats.size(), walls};
}
public void block(int startRow, int startCol) {
isInfected[startRow][startCol] = -1;
Queue<int[]> queue = new ArrayDeque<int[]>();
queue.offer(new int[]{startRow, startCol});
while (!queue.isEmpty()) {
int[] cell = queue.poll();
int row = cell[0], col = cell[1];
isInfected[row][col] = -1;
for (int[] dir : dirs) {
int newRow = row + dir[0], newCol = col + dir[1];
if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n && isInfected[newRow][newCol] == 1) {
isInfected[newRow][newCol] = -1;
queue.offer(new int[]{newRow, newCol});
}
}
}
}
public void update() {
List<int[]> newlyInfected = new ArrayList<int[]>();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (isInfected[i][j] != 0) {
continue;
}
for (int[] dir : dirs) {
int newRow = i + dir[0], newCol = j + dir[1];
if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n && isInfected[newRow][newCol] == 1) {
newlyInfected.add(new int[]{i, j});
break;
}
}
}
}
for (int[] cell : newlyInfected) {
isInfected[cell[0]][cell[1]] = 1;
}
}
}
复杂度分析
-
时间复杂度: O ( m n ( m + n ) ) O(mn(m + n)) O(mn(m+n)),其中 m m m 和 n n n 分别是网格的行数和列数。每一轮操作需要使用广度优先搜索遍历网格寻找威胁的未感染单元格最多的感染区域,然后将该感染区域隔离,将其他感染区域扩散,因此每一轮操作的时间是 O ( m n ) O(mn) O(mn),总轮数不超过 m + n m + n m+n,因此时间复杂度是 O ( m n ( m + n ) ) O(mn(m + n)) O(mn(m+n))。
-
空间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 和 n n n 分别是网格的行数和列数。队列和哈希集合需要 O ( m n ) O(mn) O(mn) 的空间。
解法二
思路和算法
也可以使用深度优先搜索实现。
遍历每个感染区域,对于每个感染区域,计算该感染区域威胁的未感染单元格数目以及隔离该感染区域需要安装的防火墙个数。
遍历整个网格之后,即可得到威胁的未感染单元格数目的最大值,以及需要安装的防火墙个数和该感染区域的位置,执行如下操作,模拟安装防火墙将该感染区域隔离以及将其他感染区域扩散。
-
将被隔离的感染区域中的值改成 − 1 -1 −1。使用深度优先搜索遍历该感染区域并更新区域中的值。
-
遍历整个网格,记录与至少一个 1 1 1 相邻的值为 0 0 0 的单元格,遍历结束之后将这些单元格的值改成 1 1 1。
重复上述操作,直到没有更多的未感染单元格被威胁时,操作结束,返回安装的防火墙个数。
代码
java
class Solution {
static int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int m, n;
int[][] isInfected;
public int containVirus(int[][] isInfected) {
int totalWalls = 0;
this.m = isInfected.length;
this.n = isInfected[0].length;
this.isInfected = isInfected;
boolean flag = true;
while (flag) {
int maxThreats = 0;
int currWalls = 0;
int startRow = -1, startCol = -1;
boolean[][] visited = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (isInfected[i][j] == 0) {
} else if (isInfected[i][j] == 1 && !visited[i][j]) {
int[] threatsWalls = getThreatsWalls(i, j, visited);
int threats = threatsWalls[0], walls = threatsWalls[1];
if (threats > maxThreats) {
maxThreats = threats;
currWalls = walls;
startRow = i;
startCol = j;
}
}
}
}
flag = maxThreats > 0;
if (flag) {
totalWalls += currWalls;
block(startRow, startCol);
update();
}
}
return totalWalls;
}
public int[] getThreatsWalls(int startRow, int startCol, boolean[][] visited) {
Set<Integer> threats = new HashSet<Integer>();
int walls = dfs(startRow, startCol, visited, threats);
return new int[]{threats.size(), walls};
}
public int dfs(int row, int col, boolean[][] visited, Set<Integer> threats) {
int walls = 0;
visited[row][col] = true;
for (int[] dir : dirs) {
int newRow = row + dir[0], newCol = col + dir[1];
if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n) {
if (isInfected[newRow][newCol] == 1 && !visited[newRow][newCol]) {
walls += dfs(newRow, newCol, visited, threats);
} else if (isInfected[newRow][newCol] == 0) {
threats.add(newRow * n + newCol);
walls++;
}
}
}
return walls;
}
public void block(int row, int col) {
isInfected[row][col] = -1;
for (int[] dir : dirs) {
int newRow = row + dir[0], newCol = col + dir[1];
if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n && isInfected[newRow][newCol] == 1) {
block(newRow, newCol);
}
}
}
public void update() {
List<int[]> newlyInfected = new ArrayList<int[]>();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (isInfected[i][j] != 0) {
continue;
}
for (int[] dir : dirs) {
int newRow = i + dir[0], newCol = j + dir[1];
if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n && isInfected[newRow][newCol] == 1) {
newlyInfected.add(new int[]{i, j});
break;
}
}
}
}
for (int[] cell : newlyInfected) {
isInfected[cell[0]][cell[1]] = 1;
}
}
}
复杂度分析
-
时间复杂度: O ( m n ( m + n ) ) O(mn(m + n)) O(mn(m+n)),其中 m m m 和 n n n 分别是网格的行数和列数。每一轮操作需要使用深度优先搜索遍历网格寻找威胁的未感染单元格最多的感染区域,然后将该感染区域隔离,将其他感染区域扩散,因此每一轮操作的时间是 O ( m n ) O(mn) O(mn),总轮数不超过 m + n m + n m+n,因此时间复杂度是 O ( m n ( m + n ) ) O(mn(m + n)) O(mn(m+n))。
-
空间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 和 n n n 分别是网格的行数和列数。递归调用栈和哈希集合需要 O ( m n ) O(mn) O(mn) 的空间。