目录
BFS解决FloodFill简介
FloodeFill算法即填充算法,中文:洪水灌溉,算法原理就是从一个点开始向四周扩散,向周围可以走到的点填充颜色,直到将可扩散到的点全部填充颜色。
通常使用下面两种方法解决FloodFill算法问题:
- BFS (宽搜)算法通常使用队列实现,将起始像素点加入队列中,并不断扩展队列中的像素点,直到队列为空为止。
- DFS(深搜) 算法通常使用递归实现,在处理当前像素点的相邻像素点时,递归调用 DFS 函数,不断深入直到无法找到相邻像素为止。
假设这一块4*4的方格是一块土地,有凸起的地方,也有凹陷的地方(凹陷的地方用负数表示)。此时下大雨发洪水,会把凹陷的地方填满。绿色圈起来的属于一块区域(上下左右四个方向,有时候题目也会问八个方向包括斜着相连的),题目会问有多少块区域被填满,或者问被填满的最大区域是哪个,或某一块区域的边长是多少。但是本质都是在一块区域找性质相同的连通块。
DFS:深度优先遍历(递归):从某一点开始一条路走到黑。以最右列的为例,从-1出发,向下->-2->-10->-12,此时发现-12的上下左右都走不了,在拐回去到-10,然后发现-10左边可以走->-4->-3。
BFS:宽度优先遍历:一层一层拨开。还是以最右边为例,从-1开始拓展一层(上下左右)发现能把-2扩展出来,接着继续扩展一层上下左右,-3和-10扩展出来;接着在扩展一层上下左右,把-4和-12扩展出来。
这时常用两个数组来遍历结点的上下左右结点:
int dx[4] = {0, 0 , -1, 1}; // i点加dx,dy就是i点的上下左右下标
int dy[4] = {1, -1 , 0, 0};
①力扣733. 图像渲染
难度 简单
有一幅以 m x n
的二维整数数组表示的图画 image
,其中 image[i][j]
表示该图画的像素值大小。
你也被给予三个整数 sr
, sc
和 newColor
。你应该从像素 image[sr][sc]
开始对图像进行 上色填充 。
为了完成 上色工作 ,从初始像素开始,记录初始坐标的 上下左右四个方向上 像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应 四个方向上 像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为 newColor
。
最后返回 经过上色渲染后的图像 。
示例 1:
输入: image = [[1,1,1],[1,1,0],[1,0,1]],sr = 1, sc = 1, newColor = 2 输出: [[2,2,2],[2,2,0],[2,0,1]] 解析: 在图像的正中间,(坐标(sr,sc)=(1,1)),在路径上所有符合条件的像素点的颜色都被更改成2。 注意,右下角的像素没有更改为2,因为它不是在上下左右四个方向上与初始点相连的像素点。
示例 2:
输入: image = [[0,0,0],[0,0,0]], sr = 0, sc = 0, newColor = 2 输出: [[2,2,2],[2,2,2]]
提示:
m == image.length
n == image[i].length
1 <= m, n <= 50
0 <= image[i][j], newColor < 216
0 <= sr < m
0 <= sc < n
class Solution {
public:
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) {
}
};
解析代码
可以利用深搜或者宽搜,遍历到与该点相连的所有像素相同的点,然后将其修改成指定的像素即可。后面深搜专题用深搜写,这里用宽搜写:
class Solution {
int dx[4] = {0, 0 , -1, 1}; // i点加dx,dy就是i点的上下左右下标
int dy[4] = {1, -1 , 0, 0};
public:
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) {
int prev = image[sr][sc];
if(prev == color)
return image;
int m = image.size(), n = image[0].size(); // 获取大小防止越界
queue<pair<int, int>> q; // 存下标
q.push({sr, sc});
while(!q.empty()) // 模拟层序遍历
{
auto [a, b] = q.front(); // 拿到队头的元素(一个pair)
q.pop();
image[a][b] = color; // 拿到的元素染上新颜色
for(int i = 0; i < 4; ++i)
{
int x = a + dx[i], y = b + dy[i];
if(x >= 0 && x < m && y >= 0 && y < n && image[x][y] == prev)
{
q.push({x, y});
}
}
}
return image;
}
};
②力扣200. 岛屿数量
难度 中等
给你一个由 '1'
(陆地)和 '0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:grid = [ ["1","1","1","1","0"], ["1","1","0","1","0"], ["1","1","0","0","0"], ["0","0","0","0","0"] ] 输出:1
示例 2:
输入:grid = [ ["1","1","0","0","0"], ["1","1","0","0","0"], ["0","0","1","0","0"], ["0","0","0","1","1"] ] 输出:3
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 300
grid[i][j]
的值为'0'
或'1'
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
}
};
解析代码
遍历整个矩阵,每次找到一块陆地的时候:说明找到一个岛屿,记录到最终结果 ret 里面。
并且将这个陆地相连的所有陆地,也就是这块岛屿,全部变成海洋。这样的话,下次遍历到这块岛屿的时候,它已经是海洋了,不会影响最终结果。
其中变成海洋的操作,可以利用深搜或宽搜解决,其实就是力扣733. 图像渲染这道题。这样,遍历完全部的矩阵的时候, ret 存的就是最终结果。
(这里用全局bool数组来判断陆地是否被遍历过,也可以直接修改数组,面试的时候可以问面试官是否可以直接修改数组)
class Solution {
int dx[4] = {0, 0 , -1, 1}; // i点加dx,dy就是i点的上下左右下标
int dy[4] = {1, -1 , 0, 0};
bool vis[301][301] = {false}; // 开题目范围大小
int m = 0, n = 0;
public:
int numIslands(vector<vector<char>>& grid) {
int ret = 0;
m = grid.size(), n = grid[0].size();
for(int i = 0; i < m; ++i)
{
for(int j = 0; j < n; ++j)
{
if(grid[i][j] == '1' && !vis[i][j])
{
++ret;
bfs(grid, i, j);
}
}
}
return ret;
}
void bfs(vector<vector<char>>& grid, int i, int j)
{
queue<pair<int, int>> q;
q.push({ i, j });
vis[i][j] = true;
while (!q.empty())
{
auto [a, b] = q.front();
q.pop();
for (int i = 0; i < 4; ++i)
{
int x = a + dx[i], y = b + dy[i];
if (x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == '1' && !vis[x][y])
{
q.push({ x, y });
vis[x][y] = true;
}
}
}
}
};
③力扣695. 岛屿的最大面积
难度 中等
给你一个大小为 m x n
的二进制矩阵 grid
。
岛屿 是由一些相邻的 1
(代表土地) 构成的组合,这里的「相邻」要求两个 1
必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid
的四个边缘都被 0
(代表水)包围着。
岛屿的面积是岛上值为 1
的单元格的数目。
计算并返回 grid
中最大的岛屿面积。如果没有岛屿,则返回面积为 0
。
示例 1:
输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]] 输出:6 解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。
示例 2:
输入:grid = [[0,0,0,0,0,0,0,0]] 输出:0
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 50
grid[i][j]
为0
或1
class Solution {
public:
int maxAreaOfIsland(vector<vector<int>>& grid) {
}
};
解析代码
遍历整个矩阵,每当遇到一块土地的时候,就用深搜或者宽搜将与这块土地相连的整个岛屿的面积计算出来。然后在搜索得到的所有的岛屿面积求一个最大值即可。
在搜索过程中,为了防止搜到重复的土地,可以开一个同等规模的bool数组,标记一下这个位置是否已经被访问过,也可以将原始矩阵的 1 修改成 0 ,但是这样操作会修改原始矩阵。
class Solution {
int dx[4] = {0, 0 , -1, 1}; // i点加dx,dy就是i点的上下左右下标
int dy[4] = {1, -1 , 0, 0};
bool vis[51][51] = {false};
int m = 0, n = 0;
public:
int maxAreaOfIsland(vector<vector<int>>& grid) {
m = grid.size(), n = grid[0].size();
int ret = 0;
for(int i = 0; i < m; ++i)
{
for(int j = 0; j < n; ++j)
{
if(grid[i][j] == 1 && !vis[i][j])
{
ret = max(ret, bfs(grid, i, j));
}
}
}
return ret;
}
int bfs(vector<vector<int>>& grid, int i, int j)
{
int ret = 0;
queue<pair<int, int>> q;
q.push({ i, j });
vis[i][j] = true; // 变成true就++ret
++ret;
while (!q.empty())
{
auto [a, b] = q.front();
q.pop();
for (int i = 0; i < 4; ++i)
{
int x = a + dx[i], y = b + dy[i];
if (x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == 1 && !vis[x][y])
{
q.push({ x, y });
vis[x][y] = true;
++ret;
}
}
}
return ret;
}
};
④力扣130. 被围绕的区域
难度 中等
给你一个 m x n
的矩阵 board
,由若干字符 'X'
和 'O'
,找到所有被 'X'
围绕的区域,并将这些区域里所有的 'O'
用 'X'
填充。
示例 1:
输入:board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]] 输出:[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]] 解释:被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
示例 2:
输入:board = [["X"]] 输出:[["X"]]
提示:
m == board.length
n == board[i].length
1 <= m, n <= 200
board[i][j]
为'X'
或'O'
class Solution {
public:
void solve(vector<vector<char>>& board) {
}
};
解析代码
正难则反。 可以先利用 bfs 将与边缘相连的 'O' 区域修改成无关字符,然后重新遍历矩阵,将没有标记过的 'O' 全修改成 'X' 即可,再把无关字符全还原成'O'。
class Solution {
int dx[4] = {0, 0, -1, 1};
int dy[4] = {1, -1, 0, 0};
int m = 0, n = 0;
public:
void solve(vector<vector<char>>& board) {
m = board.size(), n = board[0].size();
for(int i = 0; i < n; ++i) // 修改边界的O成无关字符
{
if(board[0][i] == 'O')
bfs(board, 0, i);
if(board[m - 1][i] == 'O')
bfs(board, m - 1, i);
}
for(int i = 0; i < m; ++i)
{
if(board[i][0] == 'O')
bfs(board, i, 0);
if(board[i][n - 1] == 'O')
bfs(board, i, n - 1);
}
for(int i = 0; i < m; ++i) // 把剩下的O全修改成X,R全修改成O
{
for(int j = 0; j < n; ++j)
{
if(board[i][j] == 'O')
board[i][j] = 'X';
else if(board[i][j] == 'R')
board[i][j] = 'O';
}
}
}
void bfs(vector<vector<char>>& board, int i, int j)
{
queue<pair<int, int>> q;
q.push({i, j});
board[i][j] = 'R';
while(!q.empty())
{
auto [a, b] = q.front();
q.pop();
for(int i = 0; i < 4; ++i)
{
int x = a + dx[i], y = b + dy[i];
if(x >= 0 && x < m && y >= 0 && y < n && board[x][y] == 'O')
{
q.push({x, y});
board[x][y] = 'R';
}
}
}
}
};
本篇完。
下一篇动态规划类型的是01背包问题的OJ。
下下篇是BFS解决最短路类型的OJ。