[LeetCode解题报告] 749. 隔离病毒

一、 题目

1. 题目描述

  1. 隔离病毒

难度:困难

病毒扩散得很快,现在你的任务是尽可能地通过安装防火墙来隔离病毒。

假设世界由 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
  • 在整个描述的过程中,总有一个相邻的病毒区域,它将在下一轮 严格地感染更多未受污染的方块

2. 原题链接

链接: 749. 隔离病毒

二、 解题报告

1. 思路分析

 这题好像是很久以前一道周赛。群里有佬翻出了当初的惨状。看到第一都要做那么久哈哈哈。

在这里插入图片描述

  • 一下子没思路,看了官解才有思路的。
  • 其实就是每次操作处理连通块,处理N次直到没有一块病毒区会继续扩散或者满了(也不会扩散)。
  • 每次划分连通块时,记录这块的病毒区集合、边界能扩散的坐标集合(用来判断处理哪块影响最大的)、需要建的墙数。
  • 然后模拟即可。

2. 复杂度分析

最坏时间复杂度O(nm(n+m))*

3. 代码实现

BFS

DIRS=[
    (1,0),
    (-1,0),
    (0,1),
    (0,-1),
]
class Solution:
    def containVirus(self, isInfected: List[List[int]]) -> int:
        grid = isInfected
        m,n = len(grid), len(grid[0])
        def in_bound(x,y):
            return 0<=x<m and 0<=y<n
        def bfs(x,y):  # 处理一个连通块
            q = deque([(x,y)])
            visited.add((x,y))
            ones = set()  # 病毒区
            bound0s = set()  # 病毒区相邻的0
            walls = 0  # 隔离病毒要立的墙数
            while q:
                x,y = q.popleft()
                ones.add((x,y))
                # print(q,ones)
                for dx,dy in DIRS:
                    a,b = x+dx,y+dy 
                    if not in_bound(a,b):
                        continue 
                    if grid[a][b] == 0:
                        bound0s.add((a,b))
                        walls += 1
                    elif grid[a][b] == 1 and (a,b) not in visited:
                        visited.add((a,b))
                        q.append((a,b))
            return ones,bound0s,walls
        
        # 每次遍历整个矩阵,分割连通块,记录每个连通块的病毒位置、边界扩散的0、需要加的墙数
        # 每次处理封住边界扩散0最多的连通块,然后把这个区域的病毒都置2防止下次遍历处理它
        # 如果没有找到连通块说明所有病毒都被封住了、或者是整个世界都是病毒
        ans = 0
        while True:  # 每次查找所有病毒连通块,记录每个连通块的
            visited = set()
            areas = []
            for i in range(m):
                for j in range(n):
                    if grid[i][j] == 1 and (i,j) not in visited:
                        # ones,bound0s,walls = bfs(i,j)
                        areas.append(bfs(i,j))
            # print(areas)
            
            if not areas:  # 没发现可扩散区域,说明所有病毒被框住了
                break

            # 以下不排序处理,代码长,运行快一些
            # mx_bound_i = 0  # 找边界影响最多的连通块处理它,其实sort代码更短
            # for i in range(1,len(areas)):
            #     if len(areas[i][1]) > len(areas[mx_bound_i][1]):
            #         mx_bound_i = i 
            # ones,bound0s,walls = areas[mx_bound_i]
            # ans += walls  # 立墙
            # for x,y in ones:  # 把这快病毒区置2防止下次搜到它
            #     grid[x][y] = 2
            # for i,(_,bound0s,_) in enumerate(areas):  # 其余病毒区扩散
            #     if i != mx_bound_i:
            #         for x,y in bound0s:
            #             grid[x][y] = 1

            # 以下排序处理,代码短,虽然复杂度上升,但实际一共没几个连通块(最小块大小是1,那么最多就25*25,但这样的话第二次就满图了,因此复杂度可控),实际运行速度表现没区别
            areas.sort(key = lambda x:len(x[1]),reverse=True)
            ones,_,walls = areas[0]
            ans += walls 
            for x,y in ones:
                grid[x][y] = 2
            for i in range(1,len(areas)):
                _,bound0s,_ = areas[i]
                for x,y in bound0s:
                    grid[x][y] = 1
            
        return ans

三、 本题小结

  1. 遇到问题不要慌,先看数据范围,能模拟的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值