题目
描述
在一个节点网络中,当且仅当 graph[i][j] = 1 时,每个节点 i 会与另一个节点 j 直接连接。
一些节点 initial 最初被恶意软件感染。只要两个节点直接连接,并且其中至少有一个节点被恶意软件感染,那么这两个节点都将被恶意软件感染。这种恶意软件的传播会一直持续直到没有更多的节点可以被这种方式感染。
假设 M(initial) 是在恶意软件停止传播之后,整个网络中被恶意软件感染的最终节点数。
我们可以从初始列表中删除一个节点。如果移除这一节点将最小化 M(initial), 则返回该节点。如果有多个节点满足条件,就返回索引最小的节点。
请注意,如果某个节点已从受感染节点的列表 initial 中删除,它以后可能仍然因恶意软件传播而受到感染。
数据范围
1 < graph.length = graph[0].length <= 300
0 <= graph[i][j] == graph[j][i] <= 1
graph[i][i] = 1
1 <= initial.length < graph.length
0 <= initial[i] < graph.length
样例
样例 1:
输入:graph = [[1,1,0],[1,1,0],[0,0,1]], initial = [0,1]
输出:0
样例 2:
输入:graph = [[1,0,0],[0,1,0],[0,0,1]], initial = [0,2]
输出:0
样例 3:
输入:graph = [[1,1,1],[1,1,1],[1,1,1]], initial = [1,2]
输出:1
分析
拿到一道题,如果一开始没有非常好的解题方法,建议采用暴力的办法,这道题暴力的方法就是枚举删除任何一个initial中的节点,然后比较被感染的节点数量,节点数量n,初始化列表中的节点数量m,外循环为枚举每个列表中的节点,内循环为删除当前的节点后统计染色的节点数量,时间复杂度O(m2n2)。
从暴力方法中优化
这个时候我们再来想优化,从暴力的方法优化,主要是看哪里重复(剪枝),会发现我们每次算被感染的节点都会有大量重复的情况,例如节点1,2,3,4,5,计算去掉1时我们要算从2,3,4,5出发的感染节点,去掉2时我们要计算从1,3,4,5出发的感染节点,这个时候3,4,5就已经重复了,这个时候我们就遍历列表把每个节点的能感染的节点数量都计算出来就好时间复杂度O(mn2),看一眼数据规模,最坏情况大概是300的3次方,也就是27*1e6,大概率是不会超时的。
思考什么样的点符合要求
但是我们现在要思考,能感染节点数量最多的节点去掉就一定是最好的情况吗,事实证明是不一定的,例如1-2-3这三个节点相连,1,3为初始列表中的节点,这个时候不论去掉1还是3,2号节点都会被感染,去掉这样的节点是没有意义的,所以我们在找连通块的时候遇到这样的点不需要计算它的连通块的数量,因为它等于上一个感染它的节点中连通块的节点数量,按照上面的例子来说计算完1的连通块,在计算3时直接把1连通块中的节点数量赋值给它就好,这个时候还要标记1和3为没意义的点,因为去掉任何一个对结果都没有改变,简单的来说就是:如果一个连通块中出现了两个病毒源的节点,那去掉他们当中的任何一个都是没有意义的。
写代码
初始化需要的变量
1.首先我们需要一个一维数组用来表示initial中各个节点所在的连通块节点的数量
2.一个一维数组用来表示当前节点是否被染过色,以及最初的病原来自哪个节点。这个变量就可以判断出哪个节点是没有意义的
3.一个一维数组表示该节点是否有意义,如果没有意义,就不需要拿它的连通块数量来计算最大值
int len=graph.size(),len1=initial.size();
vector<int> number(len,0); //节点所在连通块的节点数量
vector<int> board(len,-1); //标记该点是否被染色,值为原始病原
vector<int> vis(len,0); //标记该点是否有意义
1.dfs找连通块
dfs我们需要思考变得量和需要的量,邻接表肯定是需要的,然后当前节点的位置也需要,还有我们上面初始化的标记染色的数组(因为每次都要进行染色操作),上面的number也是需要的(因为要计算从该节点出发的连通块中的节点数量)
能做的事情
dsf首先思考能做的事情应该是遍历当前节点的邻接矩阵的一行,查看该节点和哪个节点相连,然后改变当前节点为下一个节点以此类推,条件就是相连且下一个节点没有被访问过,为什么要求下一个节点没有被访问过,例如1-2-3我们从1出发到了2,到了2之后发现2和1是相连的,这个时候我们再去访问1就造成死循环,也就是环路。
出口
连通块问题没有出口,但是我们要把每次经过的点去染色,染色的值就标记成我们最开始出发节点的下标,这样我们在后面判断这个点如果是initial中的节点时,就可以直接把它和传染给它的病毒源节点都标记为没有意义的节点
dfs代码
void dfs(vector<vector<int>> &graph, int now,
int base,vector<int> &number,vector<int> &board)
{
//出口(连通块没有出口)
board[now]=base; //将当前的节点标记病毒源的编号
//现在能做的事情
for(int i=0;i<graph.size();i++)
{
if(graph[now][i]==1&&board[i]==-1)
{
number[base]++;
dfs(graph,i,base,number,board);
}
}
}
2.外循环+剪枝
外循环就是把枚举每个列表中的节点,剪枝就是找出已经被感染的列表中的节点,1-2-3,当前节点为3时它已经被感染,所以它的连通块中节点的数量就等于1号节点,同时这两个节点也应该被标记成没有意义的节点
for(int i=0;i<len1;i++)
{
//顺便找出最小的节点编号
//(如果所有节点都没有意义直接返回initial中的最小值)
ans_index=min(ans_index,initial[i]);
if(board[initial[i]]==-1)
dfs(graph,initial[i],initial[i],number,board);
else
{
vis[initial[i]]=1; //该节点没意义
vis[board[initial[i]]]=1; //病毒源节点也没意义
number[initial[i]]=number[board[initial[i]]];
//该点所在连通块节点的数量等于,传染给它病毒源所在连通块节点数量
}
}
3.循环遍历找最大值(答案)
这时每个列表中的节点的number(所在连通块中的节点)已经都计算出来了,需要做判断的是所有有意义的节点
for(int i=0;i<len1;i++) //在所有节点中找到number最大值(答案)
{
if(vis[initial[i]]==0) //该节点必须有意义
{
if(number[initial[i]]>ans_max||
number[initial[i]]==ans_max&&initial[i]<ans_index)
{
ans_max=number[initial[i]];
ans_index=initial[i];
}
}
}
完整代码
class Solution {
public:
//1.计算每个连通块的节点数量
//2.标记没有意义的点(删除该节点不会影响最终感染的节点数量)
//3.遍历所有有意义的节点找出最大值
//now:现在所在的节点,base:病毒源节点
void dfs(vector<vector<int>> &graph, int now,
int base,vector<int> &number,vector<int> &board)
{
//出口(连通块没有出口)
board[now]=base; //将当前的节点标记病毒源的编号
//现在能做的事情
for(int i=0;i<graph.size();i++)
{
if(graph[now][i]==1&&board[i]==-1)
{
number[base]++;
dfs(graph,i,base,number,board);
}
}
}
int minMalwareSpread(vector<vector<int>> &graph, vector<int> &initial)
{
int len=graph.size(),len1=initial.size();
vector<int> number(len,0); //节点所在连通块的节点数量
vector<int> board(len,-1); //标记该点是否被染色,值为原始病原
vector<int> vis(len,0); //标记该点是否有意义
int ans_max=-1;
int ans_index=graph.size()+1;
for(int i=0;i<len1;i++)
{
//顺便找出最小的节点编号
//(如果所有节点都没有意义直接返回initial中的最小值)
ans_index=min(ans_index,initial[i]);
if(board[initial[i]]==-1)
dfs(graph,initial[i],initial[i],number,board);
else
{
vis[initial[i]]=1; //该节点没意义
vis[board[initial[i]]]=1; //病毒源节点也没意义
number[initial[i]]=number[board[initial[i]]];
//该点所在连通块节点的数量等于,传染给它病毒源所在连通块节点数量
}
}
for(int i=0;i<len1;i++) //在所有节点中找到number最大值(答案)
{
if(vis[initial[i]]==0) //该节点必须有意义
{
if(number[initial[i]]>ans_max||
number[initial[i]]==ans_max&&initial[i]<ans_index)
{
ans_max=number[initial[i]];
ans_index=initial[i];
}
}
}
return ans_index;
}
};
总结:
对于dfs较难的题目,首先想出暴力的方法(大佬除外),然后分析重复的情况进行优化(剪枝)。dfs的设计,首先分析当前能做的事情,最后考虑出口条件