Skip to content

Commit ccd0110

Browse files
Merge pull request SharingSource#601 from SharingSource/ac_oier
✨feat: add 749
2 parents 3ddabc8 + a2694a0 commit ccd0110

File tree

3 files changed

+225
-0
lines changed

3 files changed

+225
-0
lines changed

Index/图论 BFS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
| [417. 太平洋大西洋水流问题](https://leetcode-cn.com/problems/pacific-atlantic-water-flow/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/pacific-atlantic-water-flow/solution/by-ac_oier-do7d/) | 中等 | 🤩🤩🤩🤩🤩 |
66
| [433. 最小基因变化](https://leetcode-cn.com/problems/minimum-genetic-mutation/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/minimum-genetic-mutation/solution/by-ac_oier-74b4/) | 中等 | 🤩🤩🤩🤩🤩 |
77
| [675. 为高尔夫比赛砍树](https://leetcode.cn/problems/cut-off-trees-for-golf-event/) | [LeetCode 题解链接](https://leetcode.cn/problems/cut-off-trees-for-golf-event/solution/by-ac_oier-ksth/) | 困难 | 🤩🤩🤩🤩🤩 |
8+
| [749. 隔离病毒](https://leetcode.cn/problems/contain-virus/) | [LeetCode 题解链接](https://leetcode.cn/problems/contain-virus/solution/by-ac_oier-l9ya/) | 困难 | 🤩🤩🤩 |
89
| [752. 打开转盘锁](https://leetcode-cn.com/problems/open-the-lock/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/open-the-lock/solution/gong-shui-san-xie-yi-ti-shuang-jie-shuan-wyr9/) | 中等 | 🤩🤩🤩🤩 |
910
| [773. 滑动谜题](https://leetcode-cn.com/problems/sliding-puzzle/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/sliding-puzzle/solution/gong-shui-san-xie-fa-hui-a-suan-fa-zui-d-3go8/) | 困难 | 🤩🤩🤩🤩 |
1011
| [815. 公交路线](https://leetcode-cn.com/problems/bus-routes/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/bus-routes/solution/gong-shui-san-xie-yi-ti-shuang-jie-po-su-1roh/) | 困难 | 🤩🤩🤩🤩 |

Index/模拟.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
| [434. 字符串中的单词数](https://leetcode-cn.com/problems/number-of-segments-in-a-string/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/number-of-segments-in-a-string/solution/gong-shui-san-xie-jian-dan-zi-fu-mo-ni-t-0gx6/) | 简单 | 🤩🤩🤩🤩 |
5555
| [440. 字典序的第K小数字](https://leetcode-cn.com/problems/k-th-smallest-in-lexicographical-order/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/k-th-smallest-in-lexicographical-order/solution/by-ac_oier-m3zl/) | 困难 | 🤩🤩 |
5656
| [443. 压缩字符串](https://leetcode-cn.com/problems/string-compression/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/string-compression/solution/gong-shui-san-xie-shuang-zhi-zhen-yuan-d-bppu/) | 中等 | 🤩🤩🤩🤩 |
57+
| [749. 隔离病毒](https://leetcode.cn/problems/contain-virus/) | [LeetCode 题解链接](https://leetcode.cn/problems/contain-virus/solution/by-ac_oier-l9ya/) | 困难 | 🤩🤩🤩 |
5758
| [451. 根据字符出现频率排序](https://leetcode-cn.com/problems/sort-characters-by-frequency/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/sort-characters-by-frequency/solution/gong-shui-san-xie-shu-ju-jie-gou-yun-yon-gst9/) | 中等 | 🤩🤩🤩🤩 |
5859
| [457. 环形数组是否存在循环](https://leetcode-cn.com/problems/circular-array-loop/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/circular-array-loop/solution/gong-shui-san-xie-yi-ti-shuang-jie-mo-ni-ag05/) | 中等 | 🤩🤩🤩🤩 |
5960
| [468. 验证IP地址](https://leetcode.cn/problems/validate-ip-address/) | [LeetCode 题解链接](https://leetcode.cn/problems/validate-ip-address/solution/by-ac_oier-s217/) | 中等 | 🤩🤩🤩🤩🤩 |
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[749. 隔离病毒](https://leetcode.cn/problems/contain-virus/solution/by-ac_oier-l9ya/)** ,难度为 **困难**
4+
5+
Tag : 「模拟」、「图论搜索」、「BFS」
6+
7+
8+
9+
病毒扩散得很快,现在你的任务是尽可能地通过安装防火墙来隔离病毒。
10+
11+
假设世界由 $m \times n$ 的二维矩阵 `isInfected` 组成,`isInfected[i][j] == 0` 表示该区域未感染病毒,而  `isInfected[i][j] == 1` 表示该区域已感染病毒。可以在任意 $2$ 个相邻单元之间的共享边界上安装一个防火墙(并且只有一个防火墙)。
12+
13+
每天晚上,病毒会从被感染区域向相邻未感染区域扩散,除非被防火墙隔离。现由于资源有限,每天你只能安装一系列防火墙来隔离其中一个被病毒感染的区域(一个区域或连续的一片区域),且该感染区域对未感染区域的威胁最大且 保证唯一 。
14+
15+
你需要努力使得最后有部分区域不被病毒感染,如果可以成功,那么返回需要使用的防火墙个数; 如果无法实现,则返回在世界被病毒全部感染时已安装的防火墙个数。
16+
17+
示例 1:
18+
![](https://assets.leetcode.com/uploads/2021/06/01/virus11-grid.jpg)
19+
```
20+
输入: 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]]
21+
22+
输出: 10
23+
24+
解释:一共有两块被病毒感染的区域。
25+
在第一天,添加 5 墙隔离病毒区域的左侧。病毒传播后的状态是:
26+
27+
第二天,在右侧添加 5 个墙来隔离病毒区域。此时病毒已经被完全控制住了。
28+
```
29+
![](https://assets.leetcode.com/uploads/2021/06/01/virus13edited-grid.jpg)
30+
示例 2:
31+
![](https://assets.leetcode.com/uploads/2021/06/01/virus2-grid.jpg)
32+
```
33+
输入: isInfected = [[1,1,1],[1,0,1],[1,1,1]]
34+
35+
输出: 4
36+
37+
解释: 虽然只保存了一个小区域,但却有四面墙。
38+
注意,防火墙只建立在两个不同区域的共享边界上。
39+
```
40+
示例 3:
41+
```
42+
输入: 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]]
43+
44+
输出: 13
45+
46+
解释: 在隔离右边感染区域后,隔离左边病毒区域只需要 2 个防火墙。
47+
```
48+
49+
提示:
50+
* $m == isInfected.length$
51+
* $n == isInfected[i].length$
52+
* $1 <= m, n <= 50$
53+
* `isInfected[i][j]` is either `0` or `1`
54+
* 在整个描述的过程中,总有一个相邻的病毒区域,它将在下一轮 严格地感染更多未受污染的方块 
55+
56+
---
57+
58+
### 搜索模拟
59+
60+
根据题意,我们可以按天进行模拟,设计函数 `getCnt` 用于返回当天会被安装的防火墙数量,在 `getCnt` 内部我们会进行如下操作:
61+
62+
* 找出当天「对未感染区域的威胁最大」的区域,并将该区域进行隔离(将 $1$ 设置为 $-1$);
63+
* 对其他区域,进行步长为 $1$ 的感染操作。
64+
65+
考虑如何实现 `getCnt`:我们需要以「连通块」为单位进行处理,因此每次的 `getCnt` 操作,我们先重建一个与矩阵等大的判重数组 `vis`,对于每个 $g[i][j] = 1$ 且未被 $vis[i][j]$ 标记为 `True` 的位置进行搜索,搜索过程使用 `BFS` 实现。
66+
67+
**`BFS` 过程中,我们除了统计该连通块所需要的防火墙数量 $b$ 以外,还需要额外记录当前连通块中 $1$ 的点集 `s1`(简称为原集,含义为连通块的格子集合),以及当前连通块相邻的 $0$ 的点集 `s2`(简称为扩充集,含义为将要被感染的格子集合)。**
68+
69+
根据题意,在单次的 `getCnt` 中,我们需要在所有连通块中取出其 `s2` 大小最大(对未感染区域的威胁最大)的连通块进行隔离操作,而其余连通块则进行扩充操作。
70+
71+
因此我们可以使用两个变量 `max``ans` 分别记录所有 `s2` 中的最大值,以及取得最大 `s2` 所对应连通块所需要的防火墙数量,同时需要使用两个数组 `l1``l2` 分别记录每个连通块对应的「原集」和「扩充集」,方便我们后续进行「隔离」和「感染」。
72+
73+
Java 代码:
74+
```Java
75+
class Solution {
76+
int[][] g;
77+
int n, m, ans;
78+
int[][] dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
79+
boolean[][] vis;
80+
int search(int _x, int _y, Set<Integer> s1, Set<Integer> s2) {
81+
int ans = 0;
82+
Deque<int[]> d = new ArrayDeque<>();
83+
vis[_x][_y] = true;
84+
d.addLast(new int[]{_x, _y});
85+
s1.add(_x * m + _y);
86+
while (!d.isEmpty()) {
87+
int[] info = d.pollFirst();
88+
int x = info[0], y = info[1];
89+
for (int[] di : dirs) {
90+
int nx = x + di[0], ny = y + di[1], loc = nx * m + ny;
91+
if (nx < 0 || nx >= n || ny < 0 || ny >= m || vis[nx][ny]) continue;
92+
if (g[nx][ny] == 1) {
93+
s1.add(loc);
94+
vis[nx][ny] = true;
95+
d.addLast(new int[]{nx, ny});
96+
} else if (g[nx][ny] == 0) {
97+
s2.add(loc);
98+
ans++;
99+
}
100+
}
101+
}
102+
return ans;
103+
}
104+
int getCnt() {
105+
vis = new boolean[n][m];
106+
int max = 0, ans = 0;
107+
// l1: 每个连通块的点集 s2: 每个连通块的候选感染点集
108+
List<Set<Integer>> l1 = new ArrayList<>(), l2 = new ArrayList<>();
109+
for (int i = 0; i < n; i++) {
110+
for (int j = 0; j < m; j++) {
111+
if (g[i][j] == 1 && !vis[i][j]) {
112+
// s1: 当前连通块的点集 s2: 当前连通块的候选感染点集
113+
Set<Integer> s1 = new HashSet<>(), s2 = new HashSet<>();
114+
int b = search(i, j, s1, s2), a = s2.size();
115+
if (a > max) {
116+
max = a; ans = b;
117+
}
118+
l1.add(s1); l2.add(s2);
119+
}
120+
}
121+
}
122+
for (int i = 0; i < l2.size(); i++) {
123+
for (int loc : l2.get(i).size() == max ? l1.get(i) : l2.get(i)) {
124+
int x = loc / m, y = loc % m;
125+
g[x][y] = l2.get(i).size() == max ? -1 : 1;
126+
}
127+
}
128+
return ans;
129+
}
130+
public int containVirus(int[][] _g) {
131+
g = _g;
132+
n = g.length; m = g[0].length;
133+
while (true) {
134+
int cnt = getCnt();
135+
if (cnt == 0) break;
136+
ans += cnt;
137+
}
138+
return ans;
139+
}
140+
}
141+
```
142+
TypeScript 代码:
143+
```TypeScript
144+
let g: number[][] = null
145+
let n: number = 0, m: number = 0
146+
let vis: boolean[][] = null
147+
const dirs: number[][] = [[1,0],[-1,0],[0,1],[0,-1]]
148+
function dfs(_x: number, _y: number, s1: Set<number>, s2: Set<number>): number {
149+
let he = 0, ta = 0, ans = 0
150+
let d: Array<number> = new Array<number>()
151+
s1.add(_x * m + _y)
152+
vis[_x][_y] = true
153+
d[ta++] = _x * m + _y
154+
while (he < ta) {
155+
const poll = d[he++]
156+
const x = Math.floor(poll / m), y = poll % m
157+
for (const di of dirs) {
158+
const nx = x + di[0], ny = y + di[1], loc = nx * m + ny
159+
if (nx < 0 || nx >= n || ny < 0 || ny >= m || vis[nx][ny]) continue
160+
if (g[nx][ny] == 1) {
161+
s1.add(loc)
162+
vis[nx][ny] = true
163+
d[ta++] = loc
164+
} else if (g[nx][ny] == 0) {
165+
s2.add(loc)
166+
ans++
167+
}
168+
}
169+
}
170+
return ans
171+
}
172+
function getCnt(): number {
173+
vis = new Array<Array<boolean>>(n)
174+
for (let i = 0; i < n; i++) vis[i] = new Array<boolean>(m).fill(false)
175+
let max = 0, ans = 0
176+
let l1: Array<Set<number>> = new Array<Set<number>>(), l2: Array<Set<number>> = new Array<Set<number>>()
177+
for (let i = 0; i < n; i++) {
178+
for (let j = 0; j < m; j++) {
179+
if (g[i][j] == 1 && !vis[i][j]) {
180+
let s1 = new Set<number>(), s2 = new Set<number>()
181+
const b = dfs(i, j, s1, s2), a = s2.size
182+
if (a > max) {
183+
max = a; ans = b
184+
}
185+
l1.push(s1); l2.push(s2)
186+
}
187+
}
188+
}
189+
for (let i = 0; i < l2.length; i++) {
190+
for (let loc of l2[i].size == max ? l1[i] : l2[i]) {
191+
const x = Math.floor(loc / m), y = loc % m
192+
g[x][y] = l2[i].size == max ? -1 : 1
193+
}
194+
}
195+
return ans
196+
}
197+
function containVirus(_g: number[][]): number {
198+
g = _g
199+
n = g.length; m = g[0].length
200+
let ans: number = 0
201+
while (true) {
202+
const cnt = getCnt()
203+
if (cnt == 0) break
204+
ans += cnt
205+
}
206+
return ans
207+
};
208+
```
209+
* 时间复杂度:最多有 $n + m$ 天需要模拟,每天模拟复杂度 $O(n \times m)$,整体复杂度为 $O((n + m) \times nm)$
210+
* 空间复杂度:$O(nm)$
211+
212+
---
213+
214+
### 最后
215+
216+
这是我们「刷穿 LeetCode」系列文章的第 `No.749` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
217+
218+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
219+
220+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
221+
222+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
223+

0 commit comments

Comments
 (0)