200. 岛屿数量
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
方法一:深度优先搜索
- 将二维网格看成一个无向图,竖直或水平相邻的1之间有边相连。
- 扫描整个二维网格,遍历每个元素。
- 如果该位置为
1
,就将它作为起始节点开始深度优先搜索,并将其置为0
,然后考察该节点的上下左右结点,看是否属于岛屿部分 - 经过上一步骤的深度优先搜索,将属于同一岛屿内的元素都置为了
0
,遍历网格再遇到的1
属于新的岛屿,岛屿数加一 - 最终的岛屿数就是进行深度优先搜索的次数
代码
class Solution {
private:
void dfs(vector<vector<char>>& grid, int r, int c) {
//深度优先搜索
int nr = grid.size(); //获得数组的行数
int nc = grid[0].size();//列数
//将当前格的值设为0,表示已经遍历过了
grid[r][c] = '0';
//遍历上下左右四个
if (r - 1 >= 0 && grid[r-1][c] == '1') dfs(grid, r - 1, c);
if (r + 1 < nr && grid[r+1][c] == '1') dfs(grid, r + 1, c);
if (c - 1 >= 0 && grid[r][c-1] == '1') dfs(grid, r, c - 1);
if (c + 1 < nc && grid[r][c+1] == '1') dfs(grid, r, c + 1);
}
public:
int numIslands(vector<vector<char>>& grid) {
int nr = grid.size();
if (!nr) return 0;
int nc = grid[0].size();
//岛屿数计数
int num_islands = 0;
//遍历每个数组元素,如果值为1,就调用dfs函数进行深度优先搜索
//深度优先搜索的过程中会将属于同一岛屿(上下左右相邻的1)的元素置为0,即下次再遇到1时说明遇到了新的岛屿,岛屿数加一
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
++num_islands;
dfs(grid, r, c);
}
}
}
return num_islands;
}
};
方法二:广度优先搜索
深度优先搜索利用递归实现,广度优先搜索利用队列实现。
- 同样 扫描整个网格,如果一个位置为
1
,就将它加入队列,然后开始广度优先搜索 - 广度优先搜索的过程中,每个搜索到的
1
都会被置为0
,直到队列为空,搜索结束。 - 最终岛屿的数量就是我们进行广度优先搜索的次数。
代码
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
int nr = grid.size();
if (!nr) return 0;
int nc = grid[0].size();
int num_islands = 0;
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
++num_islands;
grid[r][c] = '0';
queue<pair<int, int>> neighbors;
//加入队列
neighbors.push({r, c});
//广度优先搜索
while (!neighbors.empty()) {
auto rc = neighbors.front();
neighbors.pop();
int row = rc.first, col = rc.second;
//考察当前结点的上下左右是否为1,为1就加入队列,然后将其置为0,代表已经遍历过了
if (row - 1 >= 0 && grid[row-1][col] == '1') {
neighbors.push({row-1, col});
grid[row-1][col] = '0';
}
if (row + 1 < nr && grid[row+1][col] == '1') {
neighbors.push({row+1, col});
grid[row+1][col] = '0';
}
if (col - 1 >= 0 && grid[row][col-1] == '1') {
neighbors.push({row, col-1});
grid[row][col-1] = '0';
}
if (col + 1 < nc && grid[row][col+1] == '1') {
neighbors.push({row, col+1});
grid[row][col+1] = '0';
}
}
}
}
}
return num_islands;
}
};
733. 图像渲染(遍历邻接点,队列)
一个图像(二维数组),每个元素的数值在0
到65535
之间,给定坐标(sr,sc)
,表示图像渲染开始的位置,以及一个新的颜色值newColor
,如果该位置原本的值就等于新颜色值,那就不进行渲染,直接返回image
渲染从初始坐标开始,考察它的上下左右四个方向的值,如果等于初始坐标的值,就对他进行渲染,然后再看它的邻接点,返回经过上色渲染后的图像
思路
利用广度优先遍历
- 用一个辅助队列来保存没有访问过并且值等于初始坐标值的点
- 官方题解对于访问邻接点有个很好的方法,先定义两个数组
const int dx[4] = {1, 0, 0, -1};
const int dy[4] = {0, 1, -1, 0};
- 然后只需要在中心坐标的基础上加数组元素,就能实现上下左右四个邻接点的遍历
for (int i = 0; i < 4; i++) {
int mx = x + dx[i], my = y + dy[i];
if (mx >= 0 && mx < n && my >= 0 && my < m && image[mx][my] == currColor) {
que.emplace(mx, my);
image[mx][my] = newColor;
}
}
代码
class Solution {
public:
//用于访问邻接点
const int dx[4] = {1, 0, 0, -1};
const int dy[4] = {0, 1, -1, 0};
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
int currColor = image[sr][sc];
if (currColor == newColor) return image;
int n = image.size(), m = image[0].size();
//辅助队列
queue<pair<int, int>> que;
//初始点入队
que.emplace(sr, sc);
//渲染初始点
image[sr][sc] = newColor;
while (!que.empty()) {
int x = que.front().first, y = que.front().second;
que.pop();
for (int i = 0; i < 4; i++) {
//考察当前位置的上下左右坐标
int mx = x + dx[i], my = y + dy[i];
//满足条件的结点入队
if (mx >= 0 && mx < n && my >= 0 && my < m && image[mx][my] == currColor) {
que.emplace(mx, my);
image[mx][my] = newColor;
}
}
}
return image;
}
};
深度优先遍历
一条路走到黑
代码
class Solution {
public:
//上下左右坐标
int x[4]={1,0,0,-1};
int y[4]={0,1,-1,0};
//深度优先遍历
void DFS(vector<vector<int>>& image,int sr, int sc, int newColor,int old,int r,int c)
{
image[sr][sc]=newColor;
for(int i=0;i<4;i++)
{
int m_x=sr+x[i];
int m_y=sc+y[i];
if(m_x<r&&m_x>=0&&m_y<c&&m_y>=0)
{
if(image[m_x][m_y]==old)
{
DFS(image,m_x,m_y,newColor,old,r,c);
}
}
}
}
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
//深度优先,一条路走到黑
if(image[sr][sc]==newColor)
return image;
int m=image.size();
int n=image[0].size();
int oldcolor=image[sr][sc];
//启动深度优先遍历
DFS(image,sr,sc,newColor,oldcolor,m,n);
return image;
}
};
542. 01 矩阵(队列)
给定一个由 0 和 1 组成的矩阵 mat ,请输出一个大小相同的矩阵,其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。
两个相邻元素间的距离为 1
思路
- 参照官方题解
- 题目要求是对于每个1,找到离他最近的0
- 题目中可能存在多个0,为了找到离1最近的那个0,可以从0的位置开始进行广度优先搜索
- 广度优先搜索可以找到从起点到其余所有点的 最短距离,因此如果我们从 0 开始搜索,每次搜索到一个 1,就可以得到 0 到这个 11 的最短距离
- 对于多个0,将这些0全部加入队列中,并且作为已访问过的点
代码
class Solution {
public:
int dx[4]={1,0,0,-1};
int dy[4]={0,1,-1,0};
vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
//对矩阵中的每个1开启广度优先搜索,一层一层地向外扩张找0
//开启的搜索次数是该1到最近的0的距离
int r=mat.size();
int c=mat[0].size();
//结果矩阵
vector<vector<int>>res(r,vector<int>(c,0));
//已访问的结点
vector<vector<int>>V(r,vector<int>(c,0));
//辅助队列
queue<pair<int,int>>Q;
//遍历原矩阵的每个元素,将0加入队列
for(int i=0;i<r;i++)
{
for(int j=0;j<c;j++)
{
if(mat[i][j]==0)
{
Q.emplace(i,j);
V[i][j]=1;
}
}
}
while(!Q.empty())
{
int x=Q.front().first;
int y=Q.front().second;
Q.pop();
for(int i=0;i<4;i++)
{
int mx=x+dx[i];
int my=y+dy[i];
//遇到没访问过得非0点
if(mx>=0 && mx<r && my>=0 && my<c && !V[mx][my])
{
res[mx][my]=res[x][y]+1;
Q.emplace(mx,my);
V[mx][my]=1;
}
}
}
return res;
}
//广度优先搜索,找0
int BFS(vector<vector<int>>& mat,vector<vector<int>>& V,int nr,int nc)
{
int r=mat.size();
int c=mat[0].size();
//辅助队列
queue<pair<int,int>>Q;
Q.emplace(nr,nc);
V[nr][nc]=1;
int n=1;
while(!Q.empty())
{
int x=Q.front().first;
int y=Q.front().second;
Q.pop();
for(int i=0;i<4;i++)
{
int mx=x+dx[i];
int my=y+dy[i];
if(mx>=0 && mx<r && my>=0 && my<c && !V[mx][my])
{
if(mat[mx][my]==1)
{
Q.emplace(mx,my);
V[mx][my]=1;
}
else if(mat[mx][my]==0)
return n;
}
}
n++;
}
return 0;
}
};
994. 腐烂的橘子
在给定的网格中,每个单元格可以有以下三个值之一:
值 0 代表空单元格;
值 1 代表新鲜橘子;
值 2 代表腐烂的橘子。
每分钟,任何与腐烂的橘子(在 4 个正方向上)相邻的新鲜橘子都会腐烂。
返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1。
思路
- 类似上一道01矩阵,利用多源广度优先搜索来解决问题,先将所有腐烂橘子加入队列,如果一开始就没有腐烂橘子,返回0
- 每次出队时,看它周围有没有新鲜橘子(1),有的话,将它也腐蚀掉,置为2,并将这个橘子也加入队列
- 每一轮中出队的橘子是属于同一层的,每一轮结束后计数器会加1
- 一轮一轮地向外腐蚀扩张,直到再也找不到新鲜橘子可以加入队列,跳出循环
- 跳出后,检查是否还存在没有被腐蚀的新鲜橘子,如果有的话,返回-1
- 计数的时候有点小问题,我设置的是每一轮出队结束后+1,但是最后一轮出队时,腐烂橘子周围已经没有新鲜橘子可以腐蚀了,没有腐蚀的过程,多加了一个1,要减去
代码
class Solution {
public:
int dx[4]={1,0,0,-1};
int dy[4]={0,1,-1,0};
int orangesRotting(vector<vector<int>>& grid) {
//类似01矩阵那道题
int m=grid.size();
int n=grid[0].size();
int ans=0;
queue<pair<int,int>>Q;
//将腐烂橘子加入队列
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
if(grid[i][j]==2)
Q.emplace(i,j);
}
}
while(!Q.empty())
{
//每次出队的是同一轮中被腐蚀的橘子
int size=Q.size();
for(int k=0;k<size;k++)
{
int mx=Q.front().first;
int my=Q.front().second;
Q.pop();
for(int i=0;i<4;i++)
{
int x=mx+dx[i];
int y=my+dy[i];
if(x>=0 && x<m && y>=0 && y<n && grid[x][y]==1)
{
Q.emplace(x,y);
grid[x][y]=2;
}
}
}
ans++;
}
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
if(grid[i][j]==1)
return -1;
}
}
//最后一轮出队时,没有找到新鲜橘子,次数上多加了一次
if(ans)
return ans-1;
return ans;
}
};
复杂度
- 时间
O(m×n)
- 空间
O(m×n)
,队列中存放的元素最多m×n
个
695. 岛屿的最大面积
给定一个包含了一些 0
和 1
的非空二维数组 grid
。
一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1
必须在水平或者竖直方向上相邻。你可以假设 grid
的四个边缘都被 0
(代表水)包围着。
找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为0
。)
思路:
- 遍历图像每个元素,遇到
1
就启动广度优先搜索,看他周围有多少1 - 广度优先遍历中,将当前元素加入队列,计数+1
- 然后看他上下左右四个数,如果是1的话,就再加入队列
- 队列中非空时,出队,然后继续向周围扩散
- 访问过的元素都要标记为0
代码
class Solution {
public:
int x[4]={1,0,0,-1};
int y[4]={0,1,-1,0};
//广度优先
int BFS(vector<vector<int>>& grid,int sr,int sc,int s,int c)
{
int n=1;
//辅助队列
queue<pair<int,int>>Q;
Q.emplace(sr,sc);
grid[sr][sc]=0;
while(!Q.empty())
{
int i=Q.front().first;
int j=Q.front().second;
Q.pop();
for(int k=0;k<4;k++)
{
int m_x=i+x[k];
int m_y=j+y[k];
if(m_x>=0&&m_x<s&&m_y>=0&&m_y<c)
{
if(grid[m_x][m_y])
{
n++;
Q.emplace(m_x,m_y);
grid[m_x][m_y]=0;
}
}
}
}
return n;
}
int maxAreaOfIsland(vector<vector<int>>& grid) {
int m=grid.size();
int n=grid[0].size();
//广度优先遍历
int ans=0;
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
//遇到岛屿,开启广度优先搜索,看这个岛屿有多少1
if(grid[i][j]==1)
{
int tmp=BFS(grid,i,j,m,n);
ans=max(ans,tmp);
}
}
}
return ans;
}
};
复杂度
- 时间
O(n*m)
,n
是网格行数,m
是网格列数,每个网格只返问一次 - 空间
O(n*m)
,队列中最多会存放所有的土地