
🔥承渊政道: 个人主页
❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法》 《C++知识内容》 《Linux系统知识》 《算法刷题指南》 《测评文章活动推广》 《大模型语言路线学习》
✨逆境不吐心中苦,顺境不忘来时路!✨ 🎬 博主简介:

很多人在刚接触矩阵题时,都会有一种相似的感受:题目看得懂,例子也能明白,但一到真正动手写代码,就不知道该从哪里开始.尤其是遇到"连通块""岛屿数量""颜色填充""边界扩散"这类问题时,明明都是在一个二维网格里走来走去,却总觉得思路零散、方法混乱,写出来不是漏情况,就是陷入死循环.其实,这类题目背后往往藏着同一种核心思想------搜索与扩散.而在众多矩阵搜索模型中,Flood Fill(洪水填充)算法正是最经典、最基础、也最值得彻底掌握的一种.它不仅能帮助我们解决看似复杂的网格遍历问题,更重要的是,它能让我们真正建立起"从一个点出发,向四周递归扩展"的搜索直觉.很多人学 Flood Fill 时,只是记住了一个模板:找到起点,朝四个方向搜索,标记访问过的位置,然后继续递归.但如果只是停留在"会套代码"的层面,一旦题目稍微变形,比如加入边界限制、状态判断、多源扩散或者回溯选择,思路就又会变得模糊.真正的关键,从来不是背下模板,而是理解它背后的搜索逻辑:为什么能搜、往哪里搜、什么时候停、为什么不会重复搜.所以这篇文章,不只是想带你"学会一道 Flood Fill 模板题",而是想带你完成一次更重要的转变:从不会做矩阵题,到真正掌握搜索扩散思想.我们会从最基础的问题出发,拆清楚递归、搜索与回溯之间的关系,理解 Flood Fill 在矩阵中的运行机制,并通过典型题型总结出一套可以迁移、可以复用的思考框架.当你真正理解了 Flood Fill,你会发现,过去那些看起来杂乱无章的矩阵题,其实都在考同一件事:你能不能把"点"与"点之间的连接关系"想清楚,能不能把"局部扩散"转化成"整体搜索".而这,正是搜索算法最迷人的地方.废话不多说,下面跟着小编的节奏🎵一起去疯狂的学习吧!
目录
- [1.Flood Fill算法思想背景详细介绍](#1.Flood Fill算法思想背景详细介绍)
- 2.图像渲染(OJ题)
- 3.岛屿的数量(OJ题)
- 4.岛屿的最大面积(OJ题)
- 5.被围绕的区域(OJ题)
- 6.太平洋大西洋的水流问题(OJ题)
- 7.扫雷游戏(OJ题)
- 8.衣橱整理(OJ题)
1.Flood Fill算法思想背景详细介绍
在正式学习 Flood Fill 之前,我们需要先理解:它为什么会出现,它解决的到底是哪一类问题,它和递归、搜索、连通性之间又是什么关系.
从表面上看,Flood Fill 常常出现在各种"矩阵题"里,例如颜色填充、岛屿数量、区域连通、封闭区域、边界扩散等问题.但从本质上说,它并不是只属于"矩阵技巧",而是一种非常典型的 图搜索思想在二维网格中的具体体现.
1.Flood Fill 是怎么来的
"Flood Fill"这个名字,直译过来就是"洪水填充"或"洪泛填充".
你可以想象这样一个场景:
有一块由很多小方格组成的区域,其中某些方格彼此连在一起.现在你从其中一个格子倒下一滴水,这滴水会不断向四周蔓延,只要相邻位置满足某种条件,它就会继续扩散.最终,所有和起点"连通"的、符合条件的格子,都会被这股"洪水"覆盖.这就是 Flood Fill 最直观的思想来源.
它最早常见于图形处理软件中,比如绘图工具中的"油漆桶填色"功能:当你点击某个区域时,程序会自动把和这个点相连、颜色相同的整片区域全部替换成新颜色.这个过程本质上就是一次 Flood Fill.所以,Flood Fill 并不是为了刷题而发明的,它本来就是一个非常自然的计算机问题:如何从一个起点出发,把所有满足连通条件的区域一次性找出来,并进行统一处理.
2.为什么矩阵题里总能看到 Flood Fill
很多初学者会觉得矩阵题种类特别杂:
- 有的题让你数岛屿个数
- 有的题让你染色
- 有的题让你找最大连通块
- 有的题让你判断是否能走到边界
- 有的题让你把某些区域全部替换掉
看起来题意五花八门,但这些问题背后往往都隐藏着一个共同结构:
在一个二维网格中,从某个位置出发,沿着相邻关系不断扩展,寻找一个"连通区域".
而Flood Fill 正是为这种结构服务的.因为矩阵本身天然就是一个"离散的平面空间":
- 每个格子可以看作一个节点
- 相邻格子之间可以看作有边连接
- 整个矩阵就构成了一张特殊的图
于是,很多矩阵题实际上都可以转化为这样一个问题:
在这张图里,从某个点开始搜索,与它连通的所有点有哪些?
一旦你这样看问题,就会发现,所谓矩阵题,本质上并不是"数组高级技巧",而是在二维场景下做 图的遍历与搜索.
3.Flood Fill 的本质:寻找"连通块"
Flood Fill 最核心的数学本质,可以概括为四个字:
寻找连通块.
所谓"连通块",就是在某种邻接规则下,彼此可以到达的一组点.
例如在矩阵中,如果我们规定:
- 上下左右四个方向算相邻,那么这是"四连通"
- 如果连对角线也算相邻,那么这是"八连通"
那么,只要一个格子可以通过若干步相邻移动走到另一个格子,它们就属于同一个连通块.
Flood Fill所做的事情,就是:
- 选择一个起点
- 看它周围哪些格子和它连通
- 再从这些格子继续扩展
- 最终把整个连通块全部找出来
这就是为什么 Flood Fill 特别适合解决下面这类问题:
- 统计区域大小
- 判断区域是否封闭
- 修改整片区域状态
- 标记已经访问过的连通部分
- 遍历所有独立区域
所以从思想上说,Flood Fill 并不只是"填色",而是:
对某个连通区域进行整体发现、整体标记、整体处理的一种搜索策略.
4.Flood Fill 为什么经常和递归联系在一起
很多教材一讲 Flood Fill,第一反应就是递归写法.这是因为 Flood Fill 的扩散过程和递归的定义非常契合.递归最适合处理的问题,往往具有这样的特征:
一个大问题,可以拆成若干个结构相同、规模更小的子问题.
而 Flood Fill 正好满足这一点.
假设你现在站在某个格子 (x, y) 上,想要完成"从这里开始填充整块区域"的任务.那你会怎么做?
答案很自然:
- 先处理当前格子
- 然后尝试向上扩展
- 尝试向下扩展
- 尝试向左扩展
- 尝试向右扩展
而"向某个方向扩展"之后,本质上又变成了同样的问题:
从新的格子出发,继续填充与它连通的区域.也就是说,原问题和子问题的结构是完全一致的.
这就使得递归成为 Flood Fill 最自然的表达方式.它把"整体扩散"拆成了无数个"从当前点继续向外扩散"的局部动作.所以,递归并不是 Flood Fill 的本质,递归只是表达 Flood Fill 的一种非常自然的实现形式.这一点非常重要.很多人误以为 Flood Fill 就是"背一个递归模板",但实际上你真正要理解的是:
- 为什么能从当前点向四周扩展
- 为什么每个方向都要继续搜索
- 为什么已经访问过的点不能重复访问
- 为什么最终能覆盖整个连通区域
这些才是算法思想本身.递归只是把这个过程写出来而已.
5.Flood Fill 和 DFS、BFS 的关系
从算法分类上说,Flood Fill 不是一个和 DFS、BFS 并列的新算法.
更准确地说:Flood Fill 是一种问题模型,而DFS/BFS是实现这种模型的搜索方式.
也就是说,Flood Fill 想解决的是:
- 从一个起点出发
- 把所有满足条件的连通位置都找出来
至于你是怎么找出来的,可以有两种典型方式:
- 用DFS 实现 Flood Fill:DFS(深度优先搜索)会沿着一个方向不断深入,直到不能再走,再回头搜索其他方向.这很像水流先沿某条路径蔓延到底,再回过头覆盖其他区域.递归写法的Flood Fill,本质上通常就是DFS.
- 用 BFS 实现 Flood Fill:BFS(广度优先搜索)则是一层一层向外扩展,先处理离起点近的,再处理远的.
这更像水波纹从中心一圈一圈向外扩散.在某些"最短步数""多源扩散""层级传播"问题里,BFS 版本的 Flood Fill 更合适.
所以要明确:
- Flood Fill 关注的是"连通区域扩散"
- DFS/BFS关注的是"扩散的遍历顺序"
两者不是竞争关系,而是"模型"和"工具"的关系.
6.Flood Fill 背后的核心思想:扩散、覆盖、去重
如果再往深一点总结,Flood Fill 背后其实有三个关键词:
(1)扩散
从一个点出发,向邻居传播.这是搜索的起点,也是"局部影响整体"的体现.
(2)覆盖
不是只看一个点,而是要把整个连通区域都处理掉.这意味着算法关注的是"区域整体",不是单点答案.
(3)去重
任何已经访问过的位置都不能重复处理,否则会死循环或重复统计.所以 Flood Fill 一定和"标记访问"绑定在一起.
2.图像渲染(OJ题)

算法思路:
可以利用深搜或者宽搜,遍历到与该点相连的所有像素相同的点,然后将其修改成指定的像素即可.
递归函数设计:
-
参数 :
a. 原始矩阵;
b. 当前所在的位置;
c. 需要修改成的颜色.
-
函数体 :
a. 先将该位置的颜色改成指定颜色(因为我们的判断,保证每次进入递归的位置都是需要修改的位置);
b. 遍历四个方向上的位置:如果当前位置合法,并且与初试颜色相同,就递归进去.

核心代码
cpp
class Solution
{
//方向数组:定义上下左右四个移动方向(二维网格通用写法)
//dx: 行坐标偏移量 dy: 列坐标偏移量
int dx[4] = {0, 0, 1, -1}; //右、左、下、上
int dy[4] = {1, -1, 0, 0};
int m, n; //m=图像的行数 n=图像的列数
int prev; //记录起始点的原始颜色(需要被替换的颜色)
public:
//主函数:图像渲染入口
//image: 二维图像数组 sr:起始行 sc:起始列 color:目标填充颜色
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color)
{
//边界条件:如果起始点已经是目标颜色,直接返回原图像(无需染色)
if(image[sr][sc] == color) return image;
//初始化图像尺寸
m = image.size(); //获取总行数
n = image[0].size(); //获取总列数
prev = image[sr][sc]; //保存需要替换的原始颜色
//调用DFS递归函数,开始填充颜色
dfs(image, sr, sc, color);
//返回修改后的图像
return image;
}
//DFS深度优先搜索:递归染色函数
//i,j: 当前正在染色的坐标 color:目标颜色
void dfs(vector<vector<int>& image, int i, int j, int color)
{
//第一步:将当前坐标的像素染成目标颜色
image[i][j] = color;
//遍历上下左右四个方向
for(int k = 0; k < 4; k++)
{
//计算当前方向的新坐标
int x = i + dx[k];
int y = j + dy[k];
//三个判断条件:
//1. x在合法行范围内 2.y在合法列范围内 3.新坐标颜色=原始颜色(需要染色)
if(x >= 0 && x < m && y >= 0 && y < n && image[x][y] == prev)
{
//递归:对符合条件的新坐标继续染色
dfs(image, x, y, color);
}
}
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
int m, n;
int prev;
public:
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color)
{
if (image[sr][sc] == color) return image;
m = image.size();
n = image[0].size();
prev = image[sr][sc];
dfs(image, sr, sc, color);
return image;
}
void dfs(vector<vector<int>>& image, int i, int j, int color)
{
image[i][j] = color;
for (int k = 0; k < 4; k++)
{
int x = i + dx[k], y = j + dy[k];
if (x >= 0 && x < m && y >= 0 && y < n && image[x][y] == prev)
{
dfs(image, x, y, color);
}
}
}
};
// 打印二维矩阵
void printImage(const vector<vector<int>>& image)
{
for (const auto& row : image)
{
for (int val : row)
{
cout << val << " ";
}
cout << endl;
}
}
int main()
{
Solution sol;
// 测试用例1
vector<vector<int>> image1 = {
{1, 1, 1},
{1, 1, 0},
{1, 0, 1}
};
int sr1 = 1, sc1 = 1, color1 = 2;
cout << "原始图像1:" << endl;
printImage(image1);
vector<vector<int>> result1 = sol.floodFill(image1, sr1, sc1, color1);
cout << "\n染色后的图像1:" << endl;
printImage(result1);
cout << "------------------------" << endl;
// 测试用例2:起点颜色和目标颜色相同
vector<vector<int>> image2 = {
{0, 0, 0},
{0, 1, 1}
};
int sr2 = 1, sc2 = 1, color2 = 1;
cout << "原始图像2:" << endl;
printImage(image2);
vector<vector<int>> result2 = sol.floodFill(image2, sr2, sc2, color2);
cout << "\n染色后的图像2:" << endl;
printImage(result2);
cout << "------------------------" << endl;
// 测试用例3:单个点扩散
vector<vector<int>> image3 = {
{0, 0, 0},
{0, 1, 1}
};
int sr3 = 1, sc3 = 1, color3 = 2;
cout << "原始图像3:" << endl;
printImage(image3);
vector<vector<int>> result3 = sol.floodFill(image3, sr3, sc3, color3);
cout << "\n染色后的图像3:" << endl;
printImage(result3);
return 0;
}

3.岛屿的数量(OJ题)

算法思路:解法(dfs):
遍历整个矩阵,每次找到一块陆地的时候:
- 说明找到一个岛屿,记录到最终结果
ret里面; - 并且将这个陆地相连的所有陆地,也就是这块岛屿,全部变成海洋.这样的话,我们下次遍历到这块岛屿的时候,它已经是海洋了,不会影响最终结果.
- 其中变成海洋的操作,可以利用深搜和宽搜解决,其实就是图像渲染这道题
这样,当我们遍历完全部的矩阵的时候,ret 存的就是最终结果.
算法流程:
- 初始化
ret = 0,记录目前找到的岛屿数量; - 双重循环遍历二维网格,每当遇到一块陆地,标记这是一个新的岛屿,然后将这块陆地相连的陆地全部变成海洋.
递归函数的设计:
- 把当前格子标记为水;
- 向上、下、左、右四格递归寻找陆地,只有在下标位置合理的情况下,才会进入递归:
a. 下一个位置的坐标合理;
b. 并且下一个位置是陆地.

核心代码
cpp
class Solution
{
//访问标记数组:标记网格中的位置是否被遍历过,避免重复统计
vector<vector<bool>> vis;
//m:网格的行数,n:网格的列数
int m, n;
public:
//主函数:统计岛屿数量
//grid:二维字符网格,'1'代表陆地,'0'代表海水
int numIslands(vector<vector<char>>& grid)
{
//获取网格的行数和列数
m = grid.size(), n = grid[0].size();
//初始化访问数组,大小和网格一致,默认值为false(未访问)
vis = vector<vector<bool>>(m, vector<bool>(n));
//记录岛屿的总数量
int ret = 0;
//双层循环:遍历网格中的每一个位置
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
{
//核心判断:当前位置未被访问 + 是陆地
if(!vis[i][j] && grid[i][j] == '1')
{
//发现新岛屿,数量+1
ret++;
//DFS遍历:标记这个岛屿所有连通的陆地为已访问
dfs(grid, i, j);
}
}
//返回最终统计的岛屿数量
return ret;
}
//方向数组:控制上下左右四个方向移动
int dx[4] = {0, 0, -1, 1}; // 行偏移:右、左、上、下
int dy[4] = {1, -1, 0, 0}; // 列偏移
//DFS深度优先搜索:标记当前岛屿所有连通的陆地
void dfs(vector<vector<char>>& grid, int i, int j)
{
//标记当前位置为已访问
vis[i][j] = true;
//遍历上下左右四个方向
for(int k = 0; k < 4; k++)
{
//计算相邻位置的坐标
int x = i + dx[k], y = j + dy[k];
//判断条件:
//1.坐标在网格范围内(不越界)
//2.该位置未被访问
//3.该位置是陆地
if(x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && grid[x][y] == '1')
{
//递归遍历相邻的陆地
dfs(grid, x, y);
}
}
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
vector<vector<bool>> vis;
int m, n;
public:
int numIslands(vector<vector<char>>& grid)
{
m = grid.size();
n = grid[0].size();
vis = vector<vector<bool>>(m, vector<bool>(n, false));
int ret = 0;
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
if (!vis[i][j] && grid[i][j] == '1')
{
ret++;
dfs(grid, i, j);
}
}
}
return ret;
}
int dx[4] = {0, 0, -1, 1};
int dy[4] = {1, -1, 0, 0};
void dfs(vector<vector<char>>& grid, int i, int j)
{
vis[i][j] = true;
for (int k = 0; k < 4; k++)
{
int x = i + dx[k], y = j + dy[k];
if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && grid[x][y] == '1')
{
dfs(grid, x, y);
}
}
}
};
// 打印字符矩阵
void printGrid(const vector<vector<char>>& grid)
{
for (const auto& row : grid)
{
for (char ch : row)
{
cout << ch << " ";
}
cout << endl;
}
}
int main()
{
Solution sol;
// 测试用例1
vector<vector<char>> grid1 = {
{'1', '1', '1', '1', '0'},
{'1', '1', '0', '1', '0'},
{'1', '1', '0', '0', '0'},
{'0', '0', '0', '0', '0'}
};
cout << "测试用例1:" << endl;
printGrid(grid1);
cout << "岛屿数量: " << sol.numIslands(grid1) << endl;
cout << "----------------------" << endl;
// 测试用例2
vector<vector<char>> grid2 = {
{'1', '1', '0', '0', '0'},
{'1', '1', '0', '0', '0'},
{'0', '0', '1', '0', '0'},
{'0', '0', '0', '1', '1'}
};
cout << "测试用例2:" << endl;
printGrid(grid2);
cout << "岛屿数量: " << sol.numIslands(grid2) << endl;
cout << "----------------------" << endl;
// 测试用例3
vector<vector<char>> grid3 = {
{'1', '0', '1', '0', '1'},
{'0', '1', '0', '1', '0'},
{'1', '0', '1', '0', '1'}
};
cout << "测试用例3:" << endl;
printGrid(grid3);
cout << "岛屿数量: " << sol.numIslands(grid3) << endl;
cout << "----------------------" << endl;
return 0;
}

4.岛屿的最大面积(OJ题)

算法思路:解法(深搜 dfs):
- 遍历整个矩阵,每当遇到一块土地的时候,就用深搜或者宽搜将与这块土地相连的整个岛屿的面积计算出来.
- 然后在搜索得到的所有的岛屿面积求一个最大值即可.
- 在搜索过程中,为了防止搜到重复的土地:
- 可以开一个同等规模的布尔数组,标记一下这个位置是否已经被访问过;
- 也可以将原始矩阵的
1修改成0,但是这样操作会修改原始矩阵.
算法流程:
-
主函数内 :
a. 遍历整个数组,发现一块没有遍历到的土地之后,就用
dfs,将与这块土地相连的岛屿的面积求出来;b. 然后将面积更新到最终结果
ret中. -
深搜函数
dfs中 :a. 能够进到
dfs函数中,说明是一个没遍历到的位置;b. 标记一下已经遍历过,设置一个变量
S = 1(当前这个位置的面积为1),记录最终的面积;c. 上下左右遍历四个位置:如果找到一块没有遍历到的土地,就将与这块土地相连的岛屿面积累加到
S上;d. 循环结束后,
S中存的就是整块岛屿的面积,返回即可.

核心代码
cpp
class Solution
{
//访问标记数组:固定大小51x51,标记网格位置是否被遍历过(题目限制网格最大50x50)
bool vis[51][51];
//m:网格行数,n:网格列数
int m, n;
//方向数组:上下左右四个方向偏移量(二维网格DFS通用写法)
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
//统计单个岛屿的面积
int count;
public:
//主函数:求解最大岛屿面积
//grid:二维网格,1=陆地,0=水域
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(!vis[i][j] && grid[i][j] == 1)
{
count = 0; //重置单个岛屿面积计数器
dfs(grid, i, j); //DFS遍历整个岛屿,统计面积
ret = max(ret, count); //更新最大面积
}
//返回最大岛屿面积
return ret;
}
//DFS深度优先搜索:递归遍历岛屿,统计陆地数量(面积)
void dfs(vector<vector<int>>& grid, int i, int j)
{
count++; //每访问一块陆地,面积+1
vis[i][j] = true; //标记当前陆地为已访问,避免重复统计
//遍历上下左右四个方向
for(int k = 0; k < 4; k++)
{
//计算相邻位置的坐标
int x = i + dx[k], y = j + dy[k];
//判断条件:
//1.坐标不越界 2.未被访问 3.是陆地
if(x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && grid[x][y] == 1)
{
dfs(grid, x, y); //递归遍历相邻陆地
}
}
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
class Solution
{
bool vis[51][51];
int m, n;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
int count;
public:
int maxAreaOfIsland(vector<vector<int>>& grid)
{
memset(vis, false, sizeof(vis)); // 初始化访问数组
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 (!vis[i][j] && grid[i][j] == 1)
{
count = 0;
dfs(grid, i, j);
ret = max(ret, count);
}
}
}
return ret;
}
void dfs(vector<vector<int>>& grid, int i, int j)
{
count++;
vis[i][j] = true;
for (int k = 0; k < 4; k++)
{
int x = i + dx[k], y = j + dy[k];
if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && grid[x][y] == 1)
{
dfs(grid, x, y);
}
}
}
};
// 打印网格
void printGrid(const vector<vector<int>>& grid)
{
for (const auto& row : grid)
{
for (int val : row)
{
cout << val << " ";
}
cout << endl;
}
}
int main()
{
Solution sol;
// 测试用例1
vector<vector<int>> grid1 = {
{0,0,1,0,0,0,1,1},
{0,1,1,0,1,0,1,1},
{0,0,0,0,1,0,0,0},
{1,1,0,1,1,1,0,0},
{1,1,0,0,0,0,0,1}
};
cout << "测试用例1:" << endl;
printGrid(grid1);
cout << "最大岛屿面积: " << sol.maxAreaOfIsland(grid1) << endl;
cout << "------------------------" << endl;
// 测试用例2
vector<vector<int>> grid2 = {
{1,1,0,0,0},
{1,1,0,1,1},
{0,0,0,1,1},
{0,1,0,0,0}
};
cout << "测试用例2:" << endl;
printGrid(grid2);
cout << "最大岛屿面积: " << sol.maxAreaOfIsland(grid2) << endl;
cout << "------------------------" << endl;
// 测试用例3:全是海洋
vector<vector<int>> grid3 = {
{0,0,0},
{0,0,0},
{0,0,0}
};
cout << "测试用例3:" << endl;
printGrid(grid3);
cout << "最大岛屿面积: " << sol.maxAreaOfIsland(grid3) << endl;
cout << "------------------------" << endl;
// 测试用例4:全是陆地
vector<vector<int>> grid4 = {
{1,1,1},
{1,1,1},
{1,1,1}
};
cout << "测试用例4:" << endl;
printGrid(grid4);
cout << "最大岛屿面积: " << sol.maxAreaOfIsland(grid4) << endl;
cout << "------------------------" << endl;
return 0;
}

5.被围绕的区域(OJ题)

算法思路:
正难则反.可以先利⽤ dfs 将与边缘相连的 '0' 区域做上标记,然后重新遍历矩阵,将没有标记过的 '0'
修改成 'X' 即可.

核心代码
cpp
class Solution
{
//方向数组:定义上下左右四个移动方向(二维网格通用)
int dx[4] = {1, -1, 0, 0}; //行偏移:下、上、左、右
int dy[4] = {0, 0, 1, -1}; //列偏移
// m:棋盘的行数,n:棋盘的列数
int m, n;
public:
//主函数:解决被围绕的区域(LeetCode 130)
//规则:被 'X' 包围的 'O' 替换为 'X',边界上/连通边界的 'O' 保持不变
void solve(vector<vector<char>>& board)
{
//边界防护:如果棋盘为空,直接返回,避免程序报错
if (board.empty() || board[0].empty()) return;
//获取棋盘的行数和列数
m = board.size();
n = board[0].size();
//第一步:遍历【第一行和最后一行】的所有列
//找到边界上的 'O',用DFS标记所有连通的 'O'
for (int j = 0; j < n; j++)
{
if (board[0][j] == 'O') dfs(board, 0, j);
if (board[m - 1][j] == 'O') dfs(board, m - 1, j);
}
//第二步:遍历【第一列和最后一列】的所有行
//找到边界上的 'O',用DFS标记所有连通的 'O'
for (int i = 0; i < m; i++)
{
if (board[i][0] == 'O') dfs(board, i, 0);
if (board[i][n - 1] == 'O') dfs(board, i, n - 1);
}
//第三步:遍历整个棋盘,完成最终替换
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
if (board[i][j] == '.')
board[i][j] = 'O'; //临时标记的 '.' 还原为 'O'(不会被包围)
else if (board[i][j] == 'O')
board[i][j] = 'X'; //剩余的 'O' 被包围,替换为 'X'
}
}
}
//DFS 深度优先搜索:将当前及连通的 'O' 标记为临时字符 '.'
void dfs(vector<vector<char>>& board, int i, int j)
{
//标记当前位置为临时符号(区分不会被包围的O)
board[i][j] = '.';
//遍历上下左右四个方向
for (int k = 0; k < 4; k++)
{
//计算相邻格子的坐标
int x = i + dx[k], y = j + dy[k];
//判断条件:坐标不越界 + 相邻格子是 'O'
if (x >= 0 && x < m && y >= 0 && y < n && board[x][y] == 'O')
{
//递归标记连通的 'O'
dfs(board, x, y);
}
}
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
int m, n;
public:
void solve(vector<vector<char>>& board)
{
if (board.empty() || board[0].empty()) return;
m = board.size();
n = board[0].size();
for (int j = 0; j < n; j++)
{
if (board[0][j] == 'O') dfs(board, 0, j);
if (board[m - 1][j] == 'O') dfs(board, m - 1, j);
}
for (int i = 0; i < m; i++)
{
if (board[i][0] == 'O') dfs(board, i, 0);
if (board[i][n - 1] == 'O') dfs(board, i, n - 1);
}
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
if (board[i][j] == '.') board[i][j] = 'O';
else if (board[i][j] == 'O') board[i][j] = 'X';
}
}
}
void dfs(vector<vector<char>>& board, int i, int j)
{
board[i][j] = '.';
for (int k = 0; k < 4; k++)
{
int x = i + dx[k], y = j + dy[k];
if (x >= 0 && x < m && y >= 0 && y < n && board[x][y] == 'O')
{
dfs(board, x, y);
}
}
}
};
void printBoard(const vector<vector<char>>& board)
{
for (const auto& row : board)
{
for (char ch : row)
{
cout << ch << " ";
}
cout << endl;
}
}
int main()
{
Solution sol;
vector<vector<char>> board = {
{'X', 'X', 'X', 'X'},
{'X', 'O', 'O', 'X'},
{'X', 'X', 'O', 'X'},
{'X', 'O', 'X', 'X'}
};
cout << "原始棋盘:" << endl;
printBoard(board);
sol.solve(board);
cout << "\n处理后棋盘:" << endl;
printBoard(board);
return 0;
}

6.太平洋大西洋的水流问题(OJ题)

算法思路:
正难则反.如果直接去判断某⼀个位置是否既能到⼤西洋也能到太平洋,会重复遍历很多路径.我们反着来,从⼤西洋沿岸开始反向 dfs ,这样就能找出那些点可以流向⼤西洋;同理,从太平洋沿岸也反向 dfs ,这样就能找出那些点可以流向太平洋.那么,被标记两次的点,就是我们要找的结果.

核心代码
cpp
class Solution
{
//网格的行数、列数
int m, n;
//方向数组:上下左右四个方向(二维网格搜索通用)
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
public:
//主函数:寻找既能流向太平洋也能流向大西洋的坐标
//h: 高度矩阵,代表单元格的高度
vector<vector<int>> pacificAtlantic(vector<vector<int>>& h)
{
m = h.size();
n = h[0].size();
//pac: 标记能否流向太平洋(Pacific)
//atl: 标记能否流向大西洋(Atlantic)
vector<vector<bool>> pac(m, vector<bool>(n));
vector<vector<bool>> atl(m, vector<bool>(n));
//1.处理【太平洋】(上边界和左边界)
//从第一行所有列出发,能逆流到达的点都能流向太平洋
for(int j = 0; j < n; j++)
dfs(h, 0, j, pac);
//从第一列所有行出发,能逆流到达的点都能流向太平洋
for(int i = 0; i < m; i++)
dfs(h, i, 0, pac);
//2.处理【大西洋】(下边界和右边界)
//从最后一列所有行出发
for(int i = 0; i < m; i++)
dfs(h, i, n - 1, atl);
//从最后一行所有列出发
for(int j = 0; j < n; j++)
dfs(h, m - 1, j, atl);
//3.收集结果
//遍历所有坐标,如果该点既能流向太平洋又能流向大西洋,加入结果集
vector<vector<int>> ret;
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
if(pac[i][j] && atl[i][j])
ret.push_back({i, j});
return ret;
}
//DFS 深度优先搜索:逆流搜索
//h: 高度矩阵
//i,j: 当前搜索坐标
//vis: 访问标记数组,标记哪些点可以逆流到达海洋
void dfs(vector<vector<int>>& h, int i, int j, vector<vector<bool>>& vis)
{
//标记当前点已访问(表示可以从这里流向对应的海洋)
vis[i][j] = true;
//遍历上下左右四个方向
for(int k = 0; k < 4; k++)
{
//计算相邻坐标
int x = i + dx[k];
int y = j + dy[k];
//核心判断逻辑(逆流思想):
//1. 坐标不越界
//2. 该点未被访问
//3. 相邻点的高度 >= 当前点的高度 (h[x][y] >= h[i][j])
//解释:因为水是从高处流向低处,反之,逆流搜索时,只有下一个点比当前点高/等高,
//才能说明水可以从下一个点流到当前点,进而流到海洋。
if(x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && h[x][y] >= h[i][j])
{
dfs(h, x, y, vis);
}
}
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
int m, n;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
public:
vector<vector<int>> pacificAtlantic(vector<vector<int>>& h)
{
m = h.size();
n = h[0].size();
vector<vector<bool>> pac(m, vector<bool>(n, false));
vector<vector<bool>> atl(m, vector<bool>(n, false));
// 1. 从太平洋边界出发
for (int j = 0; j < n; j++) dfs(h, 0, j, pac);
for (int i = 0; i < m; i++) dfs(h, i, 0, pac);
// 2. 从大西洋边界出发
for (int i = 0; i < m; i++) dfs(h, i, n - 1, atl);
for (int j = 0; j < n; j++) dfs(h, m - 1, j, atl);
// 3. 同时能到达 pac 和 atl 的位置
vector<vector<int>> ret;
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
if (pac[i][j] && atl[i][j])
{
ret.push_back({i, j});
}
}
}
return ret;
}
void dfs(vector<vector<int>>& h, int i, int j, vector<vector<bool>>& vis)
{
if (vis[i][j]) return; // 防止重复搜索
vis[i][j] = true;
for (int k = 0; k < 4; k++)
{
int x = i + dx[k], y = j + dy[k];
if (x >= 0 && x < m && y >= 0 && y < n &&
!vis[x][y] && h[x][y] >= h[i][j])
{
dfs(h, x, y, vis);
}
}
}
};
// 打印矩阵
void printMatrix(const vector<vector<int>>& h)
{
for (const auto& row : h)
{
for (int x : row)
{
cout << x << " ";
}
cout << endl;
}
}
// 打印结果坐标
void printResult(const vector<vector<int>>& ret)
{
cout << "[";
for (int i = 0; i < ret.size(); i++)
{
cout << "[" << ret[i][0] << "," << ret[i][1] << "]";
if (i != ret.size() - 1) cout << ", ";
}
cout << "]" << endl;
}
int main()
{
Solution sol;
// 测试用例1
vector<vector<int>> heights1 = {
{1, 2, 2, 3, 5},
{3, 2, 3, 4, 4},
{2, 4, 5, 3, 1},
{6, 7, 1, 4, 5},
{5, 1, 1, 2, 4}
};
cout << "测试用例1:高度矩阵" << endl;
printMatrix(heights1);
vector<vector<int>> ans1 = sol.pacificAtlantic(heights1);
cout << "结果坐标:" << endl;
printResult(ans1);
cout << "------------------------" << endl;
// 测试用例2:单个格子
vector<vector<int>> heights2 = {
{1}
};
cout << "测试用例2:高度矩阵" << endl;
printMatrix(heights2);
vector<vector<int>> ans2 = sol.pacificAtlantic(heights2);
cout << "结果坐标:" << endl;
printResult(ans2);
cout << "------------------------" << endl;
// 测试用例3:所有高度相同
vector<vector<int>> heights3 = {
{1, 1},
{1, 1}
};
cout << "测试用例3:高度矩阵" << endl;
printMatrix(heights3);
vector<vector<int>> ans3 = sol.pacificAtlantic(heights3);
cout << "结果坐标:" << endl;
printResult(ans3);
cout << "------------------------" << endl;
return 0;
}

7.扫雷游戏(OJ题)

算法思路:
模拟类型的 dfs 题⽬.⾸先要搞懂题⽬要求,也就是游戏规则.从题⽬所给的点击位置开始,根据游戏规则,来⼀次 dfs 即可.

核心代码
cpp
class Solution
{
//8个方向数组:上下左右 + 四个斜角(扫雷需要遍历8个相邻格子)
int dx[8] = {0, 0, 1, -1, 1, 1, -1, -1};
int dy[8] = {1, -1, 0, 0, 1, -1, 1, -1};
//m:棋盘行数,n:棋盘列数
int m, n;
public:
//主函数:扫雷游戏点击更新逻辑
//board:扫雷棋盘,click:用户点击的坐标 [行, 列]
vector<vector<char>> updateBoard(vector<vector<char>>& board, vector<int>& click)
{
m = board.size(); //获取棋盘行数
n = board[0].size(); //获取棋盘列数
int x = click[0], y = click[1]; //提取点击的坐标
//情况1:点击的位置是未挖出的地雷 'M'
if(board[x][y] == 'M')
{
board[x][y] = 'X'; //将地雷标记为踩爆的状态 'X'
return board; //直接返回结果,游戏结束
}
//情况2:点击的是空方块,开始DFS递归翻开方块
dfs(board, x, y);
return board; //返回更新后的棋盘
}
//DFS深度优先搜索:递归翻开扫雷棋盘的方块
void dfs(vector<vector<char>>& board, int i, int j)
{
//统计当前格子 8 个相邻方向的地雷总数
int count = 0;
for(int k = 0; k < 8; k++)
{
//计算相邻格子的坐标
int x = i + dx[k], y = j + dy[k];
//判断:坐标合法 + 相邻格子是未挖出的地雷 'M'
if(x >= 0 && x < m && y >= 0 && y < n && board[x][y] == 'M')
{
count++; //地雷数量+1
}
}
//分支1:当前格子周围有地雷,直接标记数字,停止递归
if(count)
{
board[i][j] = count + '0'; //数字转字符(0~8)
return;
}
//分支2:当前格子周围无地雷,标记为空白方块 'B',继续递归翻开相邻方块
else
{
board[i][j] = 'B'; //标记为已翻开的空白方块
//遍历8个方向,递归翻开所有未挖出的空方块 'E'
for(int k = 0; k < 8; k++)
{
int x = i + dx[k], y = j + dy[k];
//判断:坐标合法 + 相邻格子是未挖出的空方块 'E'
if(x >= 0 && x < m && y >= 0 && y < n && board[x][y] == 'E')
{
dfs(board, x, y); //递归翻开
}
}
}
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
int dx[8] = {0, 0, 1, -1, 1, 1, -1, -1};
int dy[8] = {1, -1, 0, 0, 1, -1, 1, -1};
int m, n;
public:
vector<vector<char>> updateBoard(vector<vector<char>>& board, vector<int>& click)
{
m = board.size();
n = board[0].size();
int x = click[0], y = click[1];
if (board[x][y] == 'M')
{
board[x][y] = 'X';
return board;
}
dfs(board, x, y);
return board;
}
void dfs(vector<vector<char>>& board, int i, int j)
{
if (board[i][j] != 'E') return;
int count = 0;
for (int k = 0; k < 8; k++)
{
int x = i + dx[k], y = j + dy[k];
if (x >= 0 && x < m && y >= 0 && y < n && board[x][y] == 'M')
{
count++;
}
}
if (count > 0)
{
board[i][j] = count + '0';
return;
}
board[i][j] = 'B';
for (int k = 0; k < 8; k++)
{
int x = i + dx[k], y = j + dy[k];
if (x >= 0 && x < m && y >= 0 && y < n)
{
dfs(board, x, y);
}
}
}
};
void printBoard(const vector<vector<char>>& board)
{
for (const auto& row : board)
{
for (char ch : row)
{
cout << ch << " ";
}
cout << endl;
}
}
int main()
{
Solution sol;
vector<vector<char>> board = {
{'E', 'E', 'E', 'E', 'E'},
{'E', 'E', 'M', 'E', 'E'},
{'E', 'E', 'E', 'E', 'E'},
{'E', 'E', 'E', 'E', 'E'}
};
vector<int> click = {3, 0};
cout << "原始棋盘:" << endl;
printBoard(board);
vector<vector<char>> result = sol.updateBoard(board, click);
cout << "\n更新后棋盘:" << endl;
printBoard(result);
return 0;
}

8.衣橱整理(OJ题)

算法思路:
这道题本质上是一道 矩阵搜索 / Flood Fill 问题.
我们把衣柜看成一个 m × n 的网格,从左上角 (0, 0) 出发.由于题目规定整理师每次只能 向右 或 向下 移动一格,因此我们可以用 深度优先搜索(DFS)去遍历所有能够到达的格子.
对于一个位置 (i, j),它能够被访问需要满足三个条件:
- 没有越界
- 之前没有访问过
- digit(i) + digit(j) <= cnt
只要满足条件,就说明这个格子是可以整理的,我们将答案加一,并继续向它的右边和下边扩展搜索.
为什么只搜右和下
虽然从图搜索角度看,一个格子通常有四个方向可以走,但这道题题目已经明确说明:
- 只能向右移动一格
- 或向下移动一格
所以我们没有必要再去搜索左和上.
只保留这两个方向:
- (i, j + 1) 向右
- (i + 1, j) 向下
这样既符合题意,也能减少不必要的搜索分支.
搜索过程
- 从起点 (0, 0) 开始
- 如果当前位置合法,就标记为已访问
- 将答案 ret 加一
- 继续递归搜索右边和下边两个方向
- 最终统计所有能够到达的格子数量
剪枝思想
为了避免重复访问同一个格子,需要使用一个 vis 数组进行标记.
当某个格子已经访问过之后,后续再次搜索到它时就直接跳过.
这样可以保证:
- 每个格子最多只会被访问一次
- 搜索不会死循环
- 时间复杂度保持在线性范围内
数位和判断
题目要求只有当:
digit(i) + digit(j) <= cnt时,这个格子才需要整理.
所以我们单独写一个 check(i, j) 函数,用来计算坐标 i 和 j 的各位数字之和,并判断是否满足限制条件.
例如:
- (2, 3) 的数位和是 2 + 3 = 5
- (35, 7) 的数位和是 3 + 5 + 7 = 15
如果数位和大于 cnt,这个格子就不能进入.

核心代码
cpp
class Solution
{
int m, n, cnt; // m:行数 n:列数 cnt:坐标数位之和的最大限制值
bool vis[101][101]; // 访问标记数组,标记坐标是否被遍历过
int ret; // 统计最终能到达的合法格子数量
// 方向数组:仅允许 向右(0,1)、向下(1,0) 两个方向移动
int dx[2] = {0, 1};
int dy[2] = {1, 0};
public:
// 主函数:计算机器人能到达的衣柜格子总数
int wardrobeFinishing(int _m, int _n, int _cnt)
{
// 初始化参数
m = _m;
n = _n;
cnt = _cnt;
ret = 0;
// 初始化访问数组为 false(未访问)
memset(vis, false, sizeof(vis));
// 边界判断:起点(0,0) 本身就不满足数位和条件,直接返回0
if (!check(0, 0)) return 0;
// 从起点 (0,0) 开始DFS搜索
dfs(0, 0);
// 返回最终统计的可达格子数
return ret;
}
// DFS深度优先搜索:递归遍历所有合法可达的格子
void dfs(int i, int j)
{
ret++; // 到达一个合法格子,计数+1
vis[i][j] = true; // 标记当前坐标为已访问,避免重复遍历
// 遍历仅有的两个方向:右、下
for (int d = 0; d < 2; d++)
{
// 计算下一个坐标
int x = i + dx[d], y = j + dy[d];
// 核心判断条件:
// 1. 坐标在棋盘范围内 2. 未被访问过 3. 满足数位和限制
if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && check(x, y))
{
dfs(x, y); // 递归访问下一个合法坐标
}
}
}
// 辅助函数:校验坐标(i,j)的数位之和是否 <= cnt
bool check(int i, int j)
{
int tmp = 0;
// 计算横坐标 i 的各位数字之和
while (i)
{
tmp += i % 10; // 取个位数字
i /= 10; // 去掉个位数字
}
// 计算纵坐标 j 的各位数字之和
while (j)
{
tmp += j % 10;
j /= 10;
}
// 返回数位和是否符合限制条件
return tmp <= cnt;
}
};
完整测试代码
cpp
#include <iostream>
#include <cstring>
using namespace std;
class Solution
{
int m, n, cnt;
bool vis[101][101];
int ret;
int dx[2] = {0, 1};
int dy[2] = {1, 0};
public:
int wardrobeFinishing(int _m, int _n, int _cnt)
{
m = _m;
n = _n;
cnt = _cnt;
ret = 0;
memset(vis, false, sizeof(vis));
if (!check(0, 0)) return 0;
dfs(0, 0);
return ret;
}
void dfs(int i, int j)
{
ret++;
vis[i][j] = true;
for (int d = 0; d < 2; d++)
{
int x = i + dx[d], y = j + dy[d];
if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && check(x, y))
{
dfs(x, y);
}
}
}
bool check(int i, int j)
{
int tmp = 0;
while (i)
{
tmp += i % 10;
i /= 10;
}
while (j)
{
tmp += j % 10;
j /= 10;
}
return tmp <= cnt;
}
};
int main()
{
Solution s;
cout << "测试1: m = 4, n = 7, cnt = 5" << endl;
cout << "结果: " << s.wardrobeFinishing(4, 7, 5) << endl;
cout << "期望: 18" << endl;
cout << "----------------------" << endl;
cout << "测试2: m = 1, n = 1, cnt = 0" << endl;
cout << "结果: " << s.wardrobeFinishing(1, 1, 0) << endl;
cout << "期望: 1" << endl;
cout << "----------------------" << endl;
cout << "测试3: m = 2, n = 3, cnt = 1" << endl;
cout << "结果: " << s.wardrobeFinishing(2, 3, 1) << endl;
cout << "期望: 3" << endl;
cout << "----------------------" << endl;
cout << "测试4: m = 3, n = 1, cnt = 0" << endl;
cout << "结果: " << s.wardrobeFinishing(3, 1, 0) << endl;
cout << "期望: 1" << endl;
cout << "----------------------" << endl;
cout << "测试5: m = 10, n = 10, cnt = 2" << endl;
cout << "结果: " << s.wardrobeFinishing(10, 10, 2) << endl;
cout << "----------------------" << endl;
return 0;
}


🚀真正的勇者不是流泪的人,而是含泪奔跑的人!
敬请期待下一篇文章内容:【递归、搜索与回溯算法】(掌握记忆化搜索的核心套路)
每日心灵鸡汤:先努力优秀,再大方拥有 !
很喜欢的一句话:"生活不需要比别人过得好,但一定要比以前过得好,人生最幸福的事,不是活的像别人,而是在努力之后活的更像自己."该努力的时候,别选择安逸,连小孩都知道,想要的东西要踮起脚尖自己伸手去拿,所以不要什么都不做,还什么都想要.世上没有不劳而获的东西,空想只会是一场空,不尝试、不努力,你永远不知道什么是拥有. 先努力优秀,再大方拥有.眼界不够,看到的都是问题,格局不够,纠结的都是鸡毛蒜皮.愿你熬过万丈孤苦,藏下星辰大海,半山腰总是最拥挤的,你得去山顶看看.
