一、 题目
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 either0
or1
- 在整个描述的过程中,总有一个相邻的病毒区域,它将在下一轮 严格地感染更多未受污染的方块
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
三、 本题小结
- 遇到问题不要慌,先看数据范围,能模拟的。