2024.4.16
题目来源
我的题解
方法一 深度优先搜索
对每一个感染节点进行深度优先搜索,判断每一个联通分支中有多少个感染起始节点,找有两个以上节点并且感染起始节点数为1的联通分支,删除这些感染节点才能保证联通分支后续不会再次被感染,找到最大的有两个以上节点并且感染起始节点数为1连通分支,删除该连通分支的感染节点。若没有这样的连通分支,则返回感染节点最小序号。
时间复杂度:O( n 2 n^2 n2)
空间复杂度:O(n)
int sz,nodeSz;//分别是连通分支的总节点数和感染节点数
public int minMalwareSpread(int[][] graph, int[] initial) {
int n=graph.length;
boolean[] visited=new boolean[n];
boolean[] isInitial=new boolean[n];
int res=Integer.MAX_VALUE;
for(int x:initial){
isInitial[x]=true;
res=Math.min(res,x);
}
//上一个联通分支的总节点数
int preSz=0;
int ans=-1;
//是否存在总节点数大于1,感染节点数为1的连通分支
boolean f=false;
for(int x:initial){
if(visited[x])
continue;
sz=0;//一个联通分支的总节点数
nodeSz=0;//一个联通分支中的initial的节点数
dfs(graph,initial,visited,isInitial,x);
// 感染节点数为1,总节点数大于1 前一个连通分支的总节点数小于当前的连通分支数 前一个连通分支的总节点数等于当前的连通分支数,并且当前感染节点序号小于结果感染序号
if(nodeSz == 1 && sz > 1 && (preSz < sz ||( preSz == sz && x < ans))){
ans=x;
preSz=sz;
f=true;
}
//不存在感染节点数为1,总节点数大于1的连通分支 并且感染节点数为1,总节点数为1
if(nodeSz==1&&sz==1&&(ans==-1||x<ans)&&!f){
ans=x;
}
}
return ans<0?res:ans;
}
public void dfs(int[][] graph,int[] initial,boolean[] visited,boolean[] isInitial,int x){
visited[x]=true;
sz++;
if(isInitial[x]){
nodeSz++;
}
for(int next=0;next<graph.length;next++){
if(next!=x&&graph[x][next]==1&&!visited[next]){
dfs(graph,initial,visited,isInitial,next);
}
}
}
方法二 广度优先搜索
使用深度优先搜索 / 广度优先搜索 / 并查集的方法求解出所有的连通分量,需要记录的值为:
- 每一个节点所在的连通分量的编号,以及;
- 每一个连通分量的大小。
随后,遍历所有的感染节点,并使用一个哈希表记录每个连通分量中感染节点的数目。
最后,就可以依次考虑每一个连通分量了。注意到当有多个节点满足条件时,需要返回索引最小的节点。这里可能会出现所有的感染节点都没有任何作用的情况,因此在枚举每一个连通分量时,相比于通过编号进行枚举,更好的方法是通过遍历所有的感染节点进行枚举。我们维护答案 ans以及其可以减少的感染数 ans removed \textit{ans}_\textit{removed} ansremoved,初始时它们的值分别为 n+1(只要大于 n 即可)和 0。当枚举到感染节点 u 时:
- 计算出移除 uuu 可以减少的感染数 removed:如果 u 所在的连通分量中有超过一个感染节点,那么值 0,否则通过上面记录的连通分量的大小得到可以减少的感染数;
- 如果 ( removed , u ) (\textit{removed}, u) (removed,u)的组合在题目描述中优于 ( ans removed , ans ) (\textit{ans}_\textit{removed}, \textit{ans}) (ansremoved,ans),那么就对答案进行更新。
这样就可以兼顾「某些连通分量中恰好有一个感染节点」以及「没有连通分量中恰好有一个感染节点」这两种情况。
时间复杂度:O( n 2 n^2 n2)
空间复杂度:O(n)
public int minMalwareSpread(int[][] graph, int[] initial) {
int n = graph.length;
int[] ids = new int[n];
Map<Integer, Integer> idToSize = new HashMap<Integer, Integer>();
int id = 0;
for (int i = 0; i < n; ++i) {
if (ids[i] == 0) {
++id;
int size = 1;
Queue<Integer> queue = new ArrayDeque<Integer>();
queue.offer(i);
ids[i] = id;
while (!queue.isEmpty()) {
int u = queue.poll();
for (int v = 0; v < n; ++v) {
if (ids[v] == 0 && graph[u][v] == 1) {
++size;
queue.offer(v);
ids[v] = id;
}
}
}
idToSize.put(id, size);
}
}
Map<Integer, Integer> idToInitials = new HashMap<Integer, Integer>();
for (int u : initial) {
idToInitials.put(ids[u], idToInitials.getOrDefault(ids[u], 0) + 1);
}
int ans = n + 1, ansRemoved = 0;
for (int u : initial) {
int removed = (idToInitials.get(ids[u]) == 1 ? idToSize.get(ids[u]) : 0);
if (removed > ansRemoved || (removed == ansRemoved && u < ans)) {
ans = u;
ansRemoved = removed;
}
}
return ans;
}
方法三 并查集
利用并查集 uf 维护节点的连通关系,用一个变量 ans 记录答案,用一个变量 mx 记录当前能减少感染的最大节点数,初始时 ans=n, mx=0。
然后遍历数组 initial,用一个哈希表或者一个长度为 n 的数组 cnt 统计每个连通分量中被感染节点的个数。
接下来,再遍历数组 initial,对于每个节点 x,找到其所在的连通分量的根节点 root,如果该连通分量中只有一个被感染节点,即 cnt[root]=1,就更新答案,更新的条件是该连通分量中的节点数 sz大于 mx 或者 sz 等于 mx且 x 的值小于 ans。
最后,如果 ans 没有被更新,说明所有的连通分量中都有多个被感染节点,那么返回 initial 中的最小值,否则返回 ans。
时间复杂度:O( n 2 n^2 n2)
空间复杂度:O(n)
public int minMalwareSpread(int[][] graph, int[] initial) {
int n = graph.length;
boolean[] isInitial=new boolean[n];
int res=Integer.MAX_VALUE;
for(int x:initial){
res=Math.min(res,x);
isInitial[x]=true;
}
UF uf=new UF(n,isInitial);
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
if(i==j||graph[i][j]==0)
continue;
uf.union(i,j);
}
}
int ans=n;
int mi=n,mx=0;
int[] cnt=new int[n];
for(int x:initial){
++cnt[uf.find(x)];
mi=Math.min(mi,x);
}
for(int x:initial){
int root=uf.find(x);
if(cnt[root]==1){
int sz=uf.size(root);
if(sz>mx||(sz==mx&&x<ans)){
ans=x;
mx=sz;
}
}
}
return ans==n?mi:ans;
}
class UF{
private int count;
private int parent[];
private int[] size;
public UF(int n){
count=n;
parent=new int[n];
size=new int[n];
for (int i = 0; i < n; i++) {
parent[i]=i;
size[i]=1;
}
}
public void union(int p,int q){
int parentP=find(p);
int parentQ=find(q);
if (parentP==parentQ)
return;
if(size[parentP]>size[parentQ]){
parent[parentQ]=parentP;
size[parentP]+=size[parentQ];
}else{
parent[parentP]=parentQ;
size[parentQ]+=size[parentP];
}
count--;
}
public boolean isConnection(int p,int q){
int parentP=find(p);
int parentQ=find(q);
return parentP==parentQ;
}
public int find(int x){
if(parent[x]!=x){
parent[x]=find(parent[x]);//路径压缩
}
return parent[x];
}
public int size(int x){
return size[x];
}
public int getCount(){
return count;
}
}
有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~