2024.4.16力扣每日一题——尽量减少恶意软件的传播

题目来源

力扣每日一题;题序:924

我的题解

方法一 深度优先搜索

对每一个感染节点进行深度优先搜索,判断每一个联通分支中有多少个感染起始节点,找有两个以上节点并且感染起始节点数为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;
    }
}

有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜菜的小彭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值