news 2026/4/16 17:02:26

代码随想录算法训练营Day44 | 99.岛屿数量、100.岛屿的最大面积

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
代码随想录算法训练营Day44 | 99.岛屿数量、100.岛屿的最大面积

KamaCoder99.岛屿数量

99. 计数孤岛

1.思路

DFS 深搜解法

解决此问题的核心思想是DFS。当我们遍历网格时,每遇到一个未被访问过的陆地(1),就意味着发现了一个新的岛屿。此时,我们需要从这个点出发,通过DFS找到所有与它相连的陆地,并将它们全部标记为“已访问”,以避免重复计算。

方式一

main 和 dfs 分工明确。在 main 函数中发现新岛屿后,先标记起点 used[i][j] = true,再调用 dfs;dfs 函数 不处理传入的起点,只负责标记并递归访问其相邻节点。

dfs 函数的正确执行依赖于 main 函数的预处理。如果忘记在main中标记used[i][j],可能会导致无限递归或逻辑错误。

#include <iostream> #include <vector> using namespace std; int dir[4][2]={0,-1,-1,0,0,1,1,0}; void dfs(vector<vector<int>>&graph,vector<vector<bool>>&used,int x,int y){ for(int i=0;i<4;i++){ int nextx=x+dir[i][0]; int nexty=y+dir[i][1]; if(nextx<1 || nextx>=graph.size() || nexty<1 || nexty>=graph[0].size()) continue; // 越界了,直接跳过 if(graph[nextx][nexty]==1 && !used[nextx][nexty]){ used[nextx][nexty]=true; // 只标记并访问邻居 dfs(graph,used,nextx,nexty); } } } int main(){ int n,m;cin>>n>>m; vector<vector<int>> graph(n+1,vector<int>(m+1,0)); vector<vector<bool>> used(n+1,vector<bool>(m+1,false)); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ cin>>graph[i][j]; } } int res=0; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(graph[i][j]==1 && !used[i][j]){ res++; // 遇到没访问过的陆地,+1 used[i][j]=true; // 关键点:main负责标记起点 dfs(graph,used,i,j); } } } cout<<res; return 0; }
方式二

dfs 高度封装。在 main 函数中发现新岛屿后,直接调用 dfs,由 dfs 内部处理标记;dfs 函数先处理传入的起点(检查、标记),再递归访问其相邻节点。

main函数只需要“发现”一个新起点,然后把整个探索任务“外包”给dfs

dfs函数没有前置条件,自身逻辑完整,调用更安全,也更容易在其他场景中复用。

#include <iostream> #include <vector> using namespace std; int dir[4][2]={0,-1,-1,0,0,1,1,0}; void dfs(vector<vector<int>>&graph,vector<vector<bool>>&used,int x,int y){ if(graph[x][y]==0 || used[x][y]) return; // 终止条件:访问过的节点 或者 遇到海水 used[x][y]=true; // 标记访问过 for(int i=0;i<4;i++){ int nextx=x+dir[i][0]; int nexty=y+dir[i][1]; if(nextx<1 || nextx>=graph.size() || nexty<1 || nexty>=graph[0].size()) continue; dfs(graph,used,nextx,nexty); } } int main(){ int n,m;cin>>n>>m; vector<vector<int>> graph(n+1,vector<int>(m+1,0)); vector<vector<bool>> used(n+1,vector<bool>(m+1,false)); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ cin>>graph[i][j]; } } int res=0; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(graph[i][j]==1 && !used[i][j]){ res++; dfs(graph,used,i,j); } } } cout<<res; return 0; }

BFS 广搜解法

使用 BFS 最重要的一点就是要清楚在什么时候将一个节点标记为used=true

错误做法:从队列中取出节点时再标记。同一个节点可能被多个邻居加入队列,导致队列中出现大量重复节点,严重影响性能。

// 错误示例 while(!que.empty()){ pair<int,int> cur = que.front(); que.pop(); used[cur.first][cur.second] = true; // <-- 错误的标记时机! for(...){ // ... 检查邻居 ... if(graph[nextx][nexty]==1 && !used[nextx][nexty]){ que.push({nextx,nexty}); // 邻居A和B可能同时看到C,并都把C加入队列 } } }

正确做法:在将节点加入队列之前就立即标记。一旦一个节点被标记,任何其他邻居再看到它时,!used[nextx][nexty]条件就不满足,从而保证了每个节点最多只被加入队列一次。

// 正确示例 if(graph[nextx][nexty]==1 && !used[nextx][nexty]){ used[nextx][nexty]=true; // <-- 1. 立即标记 que.push({nextx,nexty}); // <-- 2. 再加入队列 }
#include <iostream> #include <vector> #include <queue> using namespace std; int dir[4][2]={0,-1,-1,0,0,1,1,0}; void bfs(vector<vector<int>>&graph,vector<vector<bool>>&used,int x,int y){ queue<pair<int,int>> que; que.push({x,y}); used[x][y]=true; // 只要加入队列,立刻标记 while(!que.empty()){ pair<int,int> cur = que.front();que.pop(); for(int i=0;i<4;i++){ int nextx=cur.first+dir[i][0]; int nexty=cur.second+dir[i][1]; if(nextx<1 || nextx>=graph.size() || nexty<1 || nexty>=graph[0].size()) continue; if(graph[nextx][nexty]==1 && !used[nextx][nexty]){ used[nextx][nexty]=true; // 只要加入队列立刻标记 que.push({nextx,nexty}); } } } } int main(){ int n,m;cin>>n>>m; vector<vector<int>> graph(n+1,vector<int>(m+1,0)); vector<vector<bool>> used(n+1,vector<bool>(m+1,false)); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ cin>>graph[i][j]; } } int res=0; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(graph[i][j]==1 && !used[i][j]){ res++; bfs(graph,used,i,j); } } } cout<<res; return 0; }

2.思考

方式一:隐式终止条件,函数本身不包含对当前节点的终止检查,假定传入的节点是有效的。终止条件的实现是通过在递归调用之前,严格“过滤”掉所有无效的下一步来完成的。如果没有任何一个邻居满足递归条件,for循环会正常结束,函数自然返回,从而实现了终止。

方式二:显式终止条件,函数在执行任何实质性操作之前,首先检查当前状态是否满足递归继续的条件。如果不满足,就立即终止(return),不再向下执行。

特性BFS (队列实现)DFS (递归实现)
核心结构while循环 +queue递归函数调用(隐式栈)
标记时机入队时标记递归前标记(或在递归函数内部处理)
遍历路径一层一层,逐层扩散。一条路走到黑,再回溯。
空间风险队列可能很大(岛屿很宽时)。递归可能很深(岛屿很长时),有栈溢出风险。
代码风格迭代,逻辑更“平铺直叙”。递归,代码更简洁。

3.Reference:99. 岛屿数量 | 代码随想录


KamaCoder100.最大岛屿的面积

100. 最大岛屿的面积

1.思路

DFS

写法一

隐式终止条件

DFS 处理当前节点的相邻节点,即在主函数遇到岛屿就计数为1,DFS 处理接下来的相邻陆地

#include <iostream> #include <vector> using namespace std; int res; int dir[4][2]={0,-1,-1,0,0,1,1,0}; void dfs(vector<vector<int>>&graph,vector<vector<bool>>&used,int x,int y){ for(int i=0;i<4;i++){ int nextx=x+dir[i][0]; int nexty=y+dir[i][1]; if(nextx<1 || nextx>=graph.size() || nexty<1 || nexty>=graph[0].size()) continue; if(graph[nextx][nexty]==1 && !used[nextx][nexty]){ res++; used[nextx][nexty]=true; dfs(graph,used,nextx,nexty); } } } int main(){ int n,m;cin>>n>>m; vector<vector<int>>graph(n+1,vector<int>(m+1,0)); vector<vector<bool>>used(n+1,vector<bool>(m+1,false)); int ans=0; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ cin>>graph[i][j]; } } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(graph[i][j]==1 && !used[i][j]){ res=1; // 因为dfs处理下一个节点,所以这里遇到陆地了就先计数,dfs处理接下来的相邻陆地 used[i][j]=true; dfs(graph,used,i,j); // 将与其链接的陆地都标记上 true ans=max(ans,res); } } } cout<<ans; return 0; }
写法二

显式终止条件

DFS 处理当前节点,即在主函数遇到岛屿就计数为0,DFS 处理接下来的全部陆地

#include <iostream> #include <vector> using namespace std; int res; int dir[4][2]={0,-1,-1,0,0,1,1,0}; void dfs(vector<vector<int>>&graph,vector<vector<bool>>&used,int x,int y){ if(graph[x][y]==0 || used[x][y]) return; used[x][y]=1; // 标记访问过 res++; for(int i=0;i<4;i++){ int nextx=x+dir[i][0]; int nexty=y+dir[i][1]; if(nextx<1 || nextx>=graph.size() || nexty<1 || nexty>=graph[0].size()) continue; dfs(graph,used,nextx,nexty); } } int main(){ int n,m;cin>>n>>m; vector<vector<int>>graph(n+1,vector<int>(m+1,0)); vector<vector<bool>>used(n+1,vector<bool>(m+1,false)); int ans=0; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ cin>>graph[i][j]; } } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(graph[i][j]==1 && !used[i][j]){ res=0; // 因为dfs处理当前节点,所以遇到陆地计数为0,进dfs之后在开始从1计数 dfs(graph,used,i,j); // 将与其链接的陆地都标记上 true ans=max(ans,res); } } } cout<<ans; return 0; }

BFS

每次开始探索一个新的岛屿时,面积计数器都会从1开始重新计算

#include <iostream> #include <vector> #include <queue> using namespace std; int res; int dir[4][2]={0,-1,-1,0,0,1,1,0}; void bfs(vector<vector<int>>&graph,vector<vector<bool>>&used,int x,int y){ queue<pair<int,int>>que; que.push({x,y}); used[x][y]=true; // 加入队列就意味节点是陆地可到达的点 while(!que.empty()){ pair<int,int> cur=que.front();que.pop(); for(int i=0;i<4;i++){ int nextx=cur.first+dir[i][0]; int nexty=cur.second+dir[i][1]; if(nextx<1 || nextx>=graph.size() || nexty<1 || nexty>=graph[0].size()) continue; if(graph[nextx][nexty]==1 && !used[nextx][nexty]){ res++; used[nextx][nexty]=true; que.push({nextx,nexty}); } } } } int main(){ int n,m;cin>>n>>m; vector<vector<int>>graph(n+1,vector<int>(m+1,0)); vector<vector<bool>>used(n+1,vector<bool>(m+1,false)); int ans=0; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ cin>>graph[i][j]; } } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(graph[i][j]==1 && !used[i][j]){ res=1; bfs(graph,used,i,j); // 将与其链接的陆地都标记上 true ans=max(ans,res); } } } cout<<ans; return 0; }

2.思考

这道题在 岛屿数量 这道题上整体思路没有变化,只是把计算的岛屿数量改成了计算最大的某个岛屿,思路还是很清晰的,重点掌握 DFS 的两种解法,要么使用隐式终止条件,要么使用显式终止条件,二者在主函数中调用前 res 的初始化也是不同的。

3.Reference:100. 岛屿的最大面积 | 代码随想录

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 19:49:26

Citra模拟器终极使用指南:10分钟快速上手畅玩3DS游戏

Citra模拟器终极使用指南&#xff1a;10分钟快速上手畅玩3DS游戏 【免费下载链接】citra 项目地址: https://gitcode.com/GitHub_Trending/ci/citra 还在为电脑上玩3DS游戏而烦恼吗&#xff1f;Citra模拟器作为一款功能强大的开源项目&#xff0c;让你轻松在Windows、m…

作者头像 李华
网站建设 2026/4/16 13:30:43

告别“全有或全无”!Android 17 通讯录授权迎来精细化管理

科技媒体 Android Authority 近日发布博文&#xff0c;报道称在安卓 17 系统中&#xff0c;谷歌计划原生引入的“联系人选择器”工具&#xff0c;旨在解决当前“全有或全无”的通讯录权限问题&#xff0c;从而大幅提升用户隐私保护。 Android出海援引博文介绍&#xff0c;安卓…

作者头像 李华
网站建设 2026/4/15 22:00:22

GSE宏编译器完整指南:魔兽世界玩家的终极宏编写解决方案

GSE宏编译器完整指南&#xff1a;魔兽世界玩家的终极宏编写解决方案 【免费下载链接】GSE-Advanced-Macro-Compiler GSE is an alternative advanced macro editor and engine for World of Warcraft. It uses Travis for UnitTests, Coveralls to report on test coverage and…

作者头像 李华
网站建设 2026/4/15 13:00:47

为什么真正的智能体系统,一定要引入“状态机”?

大家好&#xff0c;我是Wise&#xff0c;一个在互联网行业写了 20 多年代码的老兵。这两年 All In 智能体&#xff0c;我越做越确定一件事——所有能长期稳定运行的 Agent&#xff0c;本质上都是一台“状态机”。 不是 LLM 决定系统是否可控&#xff0c;而是“状态管理”决定你…

作者头像 李华
网站建设 2026/4/16 7:49:04

未来的公司不是“部门协作”,而是“智能体协作”

过去 20 年&#xff0c;企业组织的讨论几乎绕不开一个关键词&#xff1a;“协作”。跨部门协作、扁平化协作、敏捷协作、虚拟协作团队……每一波管理潮流&#xff0c;都在试图回答同一个问题&#xff1a;如何让人更高效地一起工作&#xff1f;然而 2025 年以后&#xff0c;这个…

作者头像 李华
网站建设 2026/4/16 12:13:57

CSP-J/S 2025 第一轮游记

前言感觉这次 CSP 打的还可以&#xff0c;达到超过分数线 1010 分的目标了。希望复赛也能拿到可观的分数。当然&#xff0c;You have no egg!。考前三天考前三天。一到机房就和 yanzixuan2024 它们打术士&#xff0c;真不错。考前两天下午 4:00&#xff0c;竞赛生颁奖啦&#x…

作者头像 李华