【Leetcode】并查集/DFS/BFS多解

题目-Leetcode 924

给出了一个由 n 个节点组成的网络,用 n × n 个邻接矩阵图 graph 表示。在节点网络中,当 graph[i][j] = 1 时,表示节点 i 能够直接连接到另一个节点 j。 一些节点 initial 最初被恶意软件感染。只要两个节点直接连接,且其中至少一个节点受到恶意软件的感染,那么两个节点都将被恶意软件感染。这种恶意软件的传播将继续,直到没有更多的节点可以被这种方式感染。假设 M(initial) 是在恶意软件停止传播之后,整个网络中感染恶意软件的最终节点数。如果从 initial 中移除某一节点能够最小化 M(initial), 返回该节点。如果有多个节点满足条件,就返回索引最小的节点。请注意,如果某个节点已从受感染节点的列表 initial 中删除,它以后仍有可能因恶意软件传播而受到感染。

并查集思路

被感染节点数等于 1  只需要恢复该被感染节点,整个连通子图将恢复正常;

被感染节点数大于 1  就算恢复该被感染节点,整个连通子图也无法恢复正常;

寻找中只有1个感染节点的最大连通子图

from collections import defaultdict

class UnionFind:
    def __init__(self, n):
        self.root = [i for i in range(n)]
        self.size = [1]*n
        self.part = n

    def find(self, x):
        if x != self.root[x]:
            # 在查询的时候合并到顺带直接根节点
            root_x = self.find(self.root[x])
            self.root[x] = root_x
            return root_x
        return x

    def union(self, x, y):
        root_x = self.find(x)
        root_y = self.find(y)
        if root_x == root_y: return
        if self.size[root_x] >= self.size[root_y]:
            root_x, root_y = root_y, root_x
        self.root[root_x] = root_y
        self.size[root_y] += self.size[root_x]   # 将x合并在y中
        self.size[root_x] = 0   # 将非根节点的秩赋 0
        self.part -= 1          # 记录根节点个数
        return

    def is_connected(self, x, y):
        return self.find(x) == self.find(y)

    def get_root_part(self):
        # 获取每个根节点对应的组
        part = defaultdict(list)
        n = len(self.root)
        for i in range(n):
            part[self.find(i)].append(i)
        return part

    def get_root_size(self):
        # 获取每个根节点对应的组大小
        size = defaultdict(int)
        n = len(self.root)
        for i in range(n):
            size[self.find(i)] = self.size[self.find(i)]
        return size


class Solution:
    def minMalwareSpread(self, graph, initial):
        n = len(graph)
        uf = UnionFind(n)   # 创建初始集合
        for i in range(n):  # 遍历连接关系合并部分集合
            for j in range(i):
                if graph[i][j]: uf.union(i, j)

        maxlen, idx = -1, -1
        virus = set(initial)
        parts = uf.get_root_part()  # 合并后每个集合的元素
        for root in parts:             # 遍历每个集合
            cnt, length = 0, 0
            for i in parts[root]:      # 遍历同个集合中的每个元素
                length += 1
                if i in virus:
                    cnt += 1
                    cur = i
            if cnt == 1:               # 只有一个感染节点
                if length > maxlen:
                    maxlen = length
                    idx = cur 
                elif length == maxlen:  # 相同长度选序号更小的
                    idx = min(idx, cur)

        if idx==-1: idx = min(initial)  # 没有只感染一个节点的集合
        return idx
DFS思路

记录下已访问过的节点,该集合的元素个数以及该集合中初始感染的元素;

from collections import defaultdict

class Solution:
    def minMalwareSpread(self, graph, initial):

        n = len(graph)
        edge = defaultdict(list)         # 构建邻接表
        for i in range(n):
            for j in range(i):
                if graph[i][j]:
                    edge[i].append(j)    # 双向
                    edge[j].append(i)

        visited = [False] * n
        initial = set(initial)

        def dfs(node, length, cnt):
            length += 1
            visited[node] = True
            if node in initial: 
                cnt += 1
            if node in edge:
                for nxt in edge[node]:
                    if not visited[nxt]:
                        length, cnt = dfs(nxt, length, cnt)
                        # length += nxtlength
                        # cnt += nxtcnt
            return length, cnt

        maxlen, idx = -1, -1

        for node in initial:            
            if visited[node]: continue    # 已被访问
            length, cnt = dfs(node, 0, 0)

            if cnt == 1:               # 只有一个感染节点
                if length > maxlen:
                    maxlen = length
                    idx = node 
                elif length == maxlen:  # 相同长度选序号更小的
                    idx = min(idx, node)

        if idx==-1: idx = min(initial)  # 没有只感染一个节点的集合

        return idx
BFS思路

 模拟移除某个节点后,统计被感染的节点数量,取最小感染量对应的移除节点

class Solution:
    def minMalwareSpread(self, graph, initial):

        n = len(graph)
        edge = defaultdict(list)         # 构建邻接表
        for i in range(n):
            for j in range(i):
                if graph[i][j]:
                    edge[i].append(j)    # 双向
                    edge[j].append(i)

        mincnt, idx = n+1, -1

        for node in initial:            
            cnt = 0
            initial.remove(node)       
            stack = initial[:]           # 假设移除某个节点,统计剩下的节点感染总数
            curnodes = set(stack)        # 便于查找

            while stack:
                tmp = []
                for curnode in stack:
                    for nxt in edge[curnode]:
                        if nxt not in curnodes:   # 没被感染就加入感染
                            curnodes.add(nxt)
                            tmp.append(nxt)
                            cnt += 1
                stack = tmp[:]

            if cnt < mincnt:
                mincnt = min(mincnt, cnt)
                idx = node
            elif cnt == mincnt:
                idx = min(idx, node)

            initial.insert(0,node)         # 还原初始感染节点,必须插入在前侧,否则会影响for循环
            
        return idx
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,我来用中文回复这个链接:https://leetcode-cn.com/tag/dynamic-programming/ 这个链接是 LeetCode 上关于动态规划的题目集合。动态规划是一种常用的算法思想,可以用来解决很多实际问题,比如最长公共子序列、背包问题、最短路径等等。在 LeetCode 上,动态规划也是一个非常重要的题型,很多题目都需要用到动态规划的思想来解决。 这个链接里包含了很多关于动态规划的题目,按照难度从简单到困难排列。每个题目都有详细的题目描述、输入输出样例、题目解析和代码实现等内容,非常适合想要学习动态规划算法的人来练习和提高自己的能力。 总之,这个链接是一个非常好的学习动态规划算法的资源,建议大家多多利用。 ### 回答2: 动态规划是一种算法思想,通常用于优化具有重叠子问题和最优子结构性质的问题。由于其成熟的数学理论和强大的实用效果,动态规划在计算机科学、数学、经济学、管理学等领域均有重要应用。 在计算机科学领域,动态规划常用于解决最优化问题,如背包问题、图像处理、语音识别、自然语言处理等。同时,在计算机网络和分布式系统中,动态规划也广泛应用于各种优化算法中,如链路优化、路由算法、网络流量控制等。 对于算法领域的程序员而言,动态规划是一种必要的技能和知识点。在LeetCode这样的程序员平台上,题目分类和标签设置十分细致和方便,方便程序员查找并深入学习不同类型的算法。 LeetCode的动态规划标签下的题目涵盖了各种难度级别和场景的问题。从简单的斐波那契数列、迷宫问题到可以用于实际应用的背包问题、最长公共子序列等,难度不断递进且话题丰富,有助于开发人员掌握动态规划的实际应用技能和抽象思维模式。 因此,深入LeetCode动态规划分类下的题目学习和练习,对于程序员的职业发展和技能提升有着重要的意义。 ### 回答3: 动态规划是一种常见的算法思想,它通过将问题拆分成子问题的方式进行求解。在LeetCode中,动态规划标签涵盖了众多经典和优美的算法问题,例如斐波那契数列、矩阵链乘法、背包问题等。 动态规划的核心思想是“记忆化搜索”,即将中间状态保存下来,避免重复计算。通常情况下,我们会使用一张二维表来记录状态转移过程中的中间值,例如动态规划求解斐波那契数列问题时,就可以定义一个二维数组f[i][j],代表第i项斐波那契数列中,第j个元素的值。 在LeetCode中,动态规划标签下有众多难度不同的问题。例如,经典的“爬楼梯”问题,要求我们计算到n级楼梯的方案数。这个问题的解法非常简单,只需要维护一个长度为n的数组,记录到达每一级楼梯的方案数即可。类似的问题还有“零钱兑换”、“乘积最大子数组”、“通配符匹配”等,它们都采用了类似的动态规划思想,通过拆分问题、保存中间状态来求解问题。 需要注意的是,动态规划算法并不是万能的,它虽然可以处理众多经典问题,但在某些场景下并不适用。例如,某些问题的状态转移过程比较复杂,或者状态转移方程中存在多个参数,这些情况下使用动态规划算法可能会变得比较麻烦。此外,动态规划算法也存在一些常见误区,例如错用贪心思想、未考虑边界情况等。 总之,掌握动态规划算法对于LeetCode的学习和解题都非常重要。除了刷题以外,我们还可以通过阅读经典的动态规划书籍,例如《算法竞赛进阶指南》、《算法与数据结构基础》等,来深入理解这种算法思想。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值