题目:
病毒扩散得很快,现在你的任务是尽可能地通过安装防火墙来隔离病毒。
假设世界由 m x n 的二维矩阵 isInfected 组成, isInfected[i][j] == 0 表示该区域未感染病毒,而 isInfected[i][j] == 1 表示该区域已感染病毒。可以在任意 2 个相邻单元之间的共享边界上安装一个防火墙(并且只有一个防火墙)。
每天晚上,病毒会从被感染区域向相邻未感染区域扩散,除非被防火墙隔离。现由于资源有限,每天你只能安装一系列防火墙来隔离其中一个被病毒感染的区域(一个区域或连续的一片区域),且该感染区域对未感染区域的威胁最大且 保证唯一 。
你需要努力使得最后有部分区域不被病毒感染,如果可以成功,那么返回需要使用的防火墙个数; 如果无法实现,则返回在世界被病毒全部感染时已安装的防火墙个数。
示例 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]]
输出: 10
解释:一共有两块被病毒感染的区域。
在第一天,添加 5 墙隔离病毒区域的左侧。病毒传播后的状态是:
第二天,在右侧添加 5 个墙来隔离病毒区域。此时病毒已经被完全控制住了。
示例 2:
输入: isInfected = [[1,1,1],[1,0,1],[1,1,1]]
输出: 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]]
输出: 13
解释: 在隔离右边感染区域后,隔离左边病毒区域只需要 2 个防火墙。
提示:
m == isInfected.length
n == isInfected[i].length
1 <= m, n <= 50
isInfected[i][j] is either 0 or 1
在整个描述的过程中,总有一个相邻的病毒区域,它将在下一轮 严格地感染更多未受污染的方块
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/contain-virus
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路:
思路简单来说就是一个循环:
遍历整个grid寻找所有未被访问的病毒点->计算该病毒点以及邻接的病毒点威胁的区域大小,从中找到最大区域->最大区域建墙,其他区域扩展
当最大威胁区域为0时,说明整个二维世界被感染或者病毒都已经被隔离了,结束循环即可。
代码:
int row=0,col=0;
const int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
class Solution {
public:
int containVirus(vector<vector<int>>& grid) {
int res=0,cnt=0;
int x=0,y=0;
row=grid.size(),col=grid[0].size();
//table是记录grid中所有的病毒块(相邻的病毒看成一个病毒块)的list结构,其中第一个元素是危险区域(威胁最大)的起始位置
list<pair<int,int>> table;
int vis[51][51];
//check的返回值是危险区域需要建墙数
while((cnt=check(table,grid))!=0){
res+=cnt;
memset(vis,0,sizeof(vis));
auto [x,y]=table.front();
table.pop_front();
//第一个是要加防火墙的块,expand值val,设置为-1,表示将该病毒区域设置为-1,表示已经被隔离,而且-1值在下次check时不会 //再次被检查,剪枝
expand(x,y,-1,vis,grid);
//其他的病毒区域会扩展,val设为1,表示与病毒相邻的区域会变成1
for(auto [x,y]:table){
expand(x,y,1,vis,grid);
}
}
return res;
}
//遍历整个grid,从中找出所有的病毒区域放到table中,并且找到威胁最大的区域放到table的第一个元素中,返回危险病毒块威胁的区域 //需要建墙数
int check(list<pair<int,int>>&table,vector<vector<int>>&grid){
bool flag=false;
int wall=0;
int vis[51][51];memset(vis,0,sizeof(vis));
for(int i=0;i<row;i++){
for(int j=0;j<col;j++){
if(grid[i][j]==1){
//vis记录已经被访问过的病毒,也即一个病毒块只会被dfs函数处理一次
if(vis[i][j]==false){
vis[i][j]=true;
//cnt是当前病毒块威胁的区域的大小
int cnt=dfs(i,j,vis,grid);
//跟新当前威胁区域的最大值
if(cnt>wall){
wall=cnt;
//需要将之前的威胁最大的病毒块首地址从第一个取出,放到最后
if(table.empty()!=true){
auto front=table.front();
table.pop_front();
table.push_back(front);
}
//当前最大值放到list的首位
table.push_front(make_pair(i,j));
}
//如果不是最大值,而且该区域可以传播(威胁的区域大于0),放到list的尾部
else if(cnt!=0){
table.push_back(make_pair(i,j));
}
}
}
}
}
//list为空,说明所有的病毒区域都不能再扩展(所有病毒被隔离,或者都被感染了)模拟过程完成
if(table.empty()==true){
return 0;
}
else{
memset(vis,0,sizeof(vis));
vis[table.front().first][table.front().second]=true;
//计算危险病毒块需要建墙数
return cnt(table.front().first,table.front().second,vis,grid);
}
}
//计算病毒区域需要建墙数
int cnt(int x,int y,int vis[51][51],vector<vector<int>>&grid){
int res=0;
for(int i=0;i<4;i++){
int nx=x+dir[i][0],ny=y+dir[i][1];
if(nx<row&&nx>=0&&ny<col&&ny>=0&&vis[nx][ny]==false){
if(grid[nx][ny]==0){
//发现一个可被感染的区域,用-1标记,防止重复计算,同时要记录位置,本次循环结束要恢复为0
res++;
}
if(grid[nx][ny]==1){
vis[nx][ny]=true;
//递归计算下一个病毒需要建墙数
res+=cnt(nx,ny,vis,grid);
}
}
}
return res;
}
int dfs(int x,int y,int vis[51][51],vector<vector<int>>&grid){
int res=0;
vector<pair<int,int>> recover;
vector<pair<int,int>> deepin;
for(int i=0;i<4;i++){
int nx=x+dir[i][0],ny=y+dir[i][1];
if(nx<row&&nx>=0&&ny<col&&ny>=0&&vis[nx][ny]==false){
if(grid[nx][ny]==0){
//发现一个可被感染的区域,vis设置为1标记,防止重复计算,同时要记录位置,本次循环结束要恢复为0
vis[nx][ny]=true;
recover.push_back(make_pair(nx,ny));
res++;
}
//以为一个健康区域可能被若干个病毒威胁,为避免重复计算,现将当前病毒威胁区域的vis都设置为0,再递归遍历下一个
//病毒区域,所以访问当前位置的邻接的区域中的健康区域要在访问病毒区域之后,所以要线记录病毒区域。
if(grid[nx][ny]==1){
vis[nx][ny]=true;
deepin.push_back(make_pair(nx,ny));
}
}
}
//递归计算当位置的邻接位置中的病毒区域
for(auto [x,y]:deepin){
res+=dfs(x,y,vis,grid);
}
//恢复原来的健康区域(因为这个vis在check中是通用的,一个病毒块的威胁区域和另一个是不相关的,所以要恢复)
for(auto [x,y]:recover){
vis[x][y]=false;
}
return res;
}
//病毒扩展,建防火墙的函数(病毒扩展时val为1,将邻接节点变为1,建墙时设置病毒区域为-1,其他不变)
void expand(int x,int y,int val,int vis[51][51],vector<vector<int>>&grid){
//如果设置了防火墙之后,原先的病毒设置为-1,防止下次check时再进行检查
grid[x][y]=val;
for(int i=0;i<4;i++){
int nx=x+dir[i][0],ny=y+dir[i][1];
if(nx>=0&&nx<row&&ny>=0&&ny<col&&vis[nx][ny]==false){
//如果当前区域是健康的,而且当前是扩展过程,则将其设置为1,表示变为病毒区域
if(grid[nx][ny]==0&&val==1){
grid[nx][ny]=1;
//设置为true为了防止扩展的区域被二次扩展,即被当做之前的病毒区域再被扩展,
vis[nx][ny]=true;
}
//如果当前位置是病毒,如果是扩展过程,设置为1(保持原值),建墙过程,设置为-1,表示已被控制,不可再扩展
else if(grid[nx][ny]==1){
vis[nx][ny]=true;
expand(nx,ny,val,vis,grid);
}
}
}
}
};