写在前面
本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更……
专栏内容以分析题目为主,并附带一些对于本题涉及到的数据结构等内容进行回顾与总结,文章结构大致如下,部分内容会有增删:
- Tag:介绍本题牵涉到的知识点、数据结构;
- 题目来源:贴上题目的链接,方便大家查找题目并完成练习;
- 题目解读:复述题目(确保自己真的理解题目意思),并强调一些题目重点信息;
- 解题思路:介绍一些解题思路,每种解题思路包括思路讲解、实现代码以及复杂度分析;
- 知识回忆:针对今天介绍的题目中的重点内容、数据结构进行回顾总结。
Tag
【图】【深搜】【广搜】【并查集】
题目来源
解题思路
本题是一道典型的图搜索问题,我们要在图中找到所有的 大 1,所谓的 大 1 就是将图中所有相邻的 1
都标记在一起,比如下图。
我们首先将相邻的 1
合并在一起,然后统计有多少这样的 大 1 存在,即为答案。
以上问题的核心步骤是如何将相邻的 1
和合并在一起,为此有三种方法:
- 深搜
- 广搜
- 并查集
方法一:深搜
思路
在 numIslands
中我们枚举网格中的每一个位置,如果某位置的字符为 1
并且没有被访问过,则说明此位置是 大 1 的一部分,我们就将统计 大 1 数量(岛屿的数量)的全局变量 ++cnt
,接着调用深搜 dfs
函数将与此位置的相连的 1 都连接起来。
最后返回 cnt
。
代码
class Solution {
public:
int m, n;
int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int visited[310][310];
void dfs(vector<vector<char>>& grid, int x, int y){
if(x < 0 || x >= m || y < 0 || y >= n || grid[x][y] == '0' || visited[x][y]){
return;
}
visited[x][y] = 1;
for(int i = 0; i < 4; ++i){
int nx = x + dir[i][0];
int ny = y + dir[i][1];
dfs(grid, nx, ny);
}
}
int numIslands(vector<vector<char>>& grid) {
m = grid.size();
n = grid[0].size();
int cnt = 0;
for(int i = 0; i < m; ++i){
for(int j = 0; j < n; ++j){
if(grid[i][j] == '1' && !visited[i][j]){
cnt++;
dfs(grid, i, j);
}
}
}
return cnt;
}
};
复杂度分析
时间复杂度: O ( m n ) O(mn) O(mn), m m m 和 n n n 分别为网格的行数和列数。
空间复杂度: O ( m n ) O(mn) O(mn)。
方法二:广搜
利用广搜也可以将相连的 1
连接起来形成 大 1。以下代码是利用广搜将遍历到的 大 1 的第一个 1
不动,与之相连的 1
置为字符 0
,从而统计岛屿的数量。
代码
class Solution {
private:
const int dirs[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
public:
int numIslands(vector<vector<char>>& grid) {
int m = grid.size(), n = grid[0].size();
int cnt = 0;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == '1') {
++cnt;
grid[i][j] = '0';
queue<pair<int, int>> nb;
nb.push({i, j});
while (!nb.empty()) {
auto ij = nb.front(); nb.pop();
for (int k = 0; k < 4; ++k) {
int nx = ij.first + dirs[k][0];
int ny = ij.second + dirs[k][1];
if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] == '1') {
grid[nx][ny] = '0';
nb.push({nx, ny});
}
}
}
}
}
}
return cnt;
}
};
复杂度分析
时间复杂度: O ( m n ) O(mn) O(mn), m m m 和 n n n 分别为网格的行数和列数。
空间复杂度: O ( m i n ( m , n ) ) O(min(m, n)) O(min(m,n)),在最坏的情况下,整个网络均为陆地,队列的大小可以达到 m i n ( m , n ) min(m, n) min(m,n)。
方法三:并查集
牵涉到 合并 的问题,一定不能忘记并查集。关于并查集方法与使用可以参考 【并查集(上)基础篇】、【并查集(下)应用篇】 这两篇文章。
落实到本题的实现中,在并查集模板中增加一个 res
变量,用来统计矩阵中 ‘1’ 的数量,在原数组中,枚举到 ‘1’ 的存在就
+
+
r
e
s
++res
++res。在合并 ‘1’ 的时候就
−
−
r
e
s
--res
−−res,最后返回
r
e
s
res
res。具体可以参考我的并查集方法代码。
代码
class Solution {
public:
const int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int numIslands(vector<vector<char>>& grid) {
int m = grid.size(), n = grid[0].size();
int res = 0;
vector<int> pa(m*n);
vector<int> rank(m*n, 0);
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == '1') {
++res;
}
pa[i * n + j] = i * n + j;
}
}
function<int(int)> find = [&](int x) {
return pa[x] == x ? x : pa[x] = find(pa[x]);
};
function<void(int, int)> unite = [&](int x, int y) {
x = find(x);
y = find(y);
if (x == y) return;
if (rank[x] < rank[y]) {
swap(x, y);
}
pa[y] = x;
if (rank[x] == rank[y]) rank[x] += 1;
--res;
};
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == '1') {
grid[i][j] == '0';
for (int k = 0; k < 4; ++k) {
int x = i + dirs[k][0];
int y = j + dirs[k][1];
if (x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == '1') {
unite(i * n + j, x * n + y);
}
}
}
}
}
return res;
}
};
并查集的时间复杂度这里不做研究,感兴趣的可以参考官方题解。
写在最后
如果您发现文章有任何错误或者对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。
如果大家有更优的时间、空间复杂度的方法,欢迎评论区交流。
最后,感谢您的阅读,如果有所收获的话可以给我点一个 👍 哦。