直接看题目,还是熟悉写法。
题目:孤岛的总面积
题目描述
给定一个由 1(陆地)和 0(水)组成的矩阵,岛屿指的是由水平或垂直方向上相邻的陆地单元格组成的区域,且完全被水域单元格包围。孤岛是那些位于矩阵内部、所有单元格都不接触边缘的岛屿。
现在你需要计算所有孤岛的总面积,岛屿面积的计算方式为组成岛屿的陆地的总数。
输入描述
第一行包含两个整数 N, M,表示矩阵的行数和列数。之后 N 行,每行包含 M 个数字,数字为 1 或者 0。
输出描述
输出一个整数,表示所有孤岛的总面积,如果不存在孤岛,则输出 0。
题目分析:
题目求得孤岛,孤岛不和边界相接。那我们把边界上的陆地变成海洋,剩下得不就只有孤岛了吗?
来看看具体得写法:
//深搜
cpp
//深搜
#include<iostream>
#include<vector>
using namespace std;
int dir[4][2]={1,0,0,1,-1,0,0,-1};
int count;
void DFS(vector<vector<int>>& grid,int x,int y){
grid[x][y]=0;//把陆地变成海洋
count++;
for(int i=0;i<4;i++){
int nextX=x+dir[i][0];
int nextY=y+dir[i][1];
if(nextX<0||nextX>=grid.size()||nextY<0||nextY>=grid[0].size()) continue;
if (grid[nextX][nextY] == 0) continue;
DFS(grid,nextX,nextY);
}
return;
}
int main(){
int n,m;
cin>>n>>m;
vector<vector<int>> grid(n,vector<int>(m,0));
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
cin>>grid[i][j];
}
}
for(int i=0;i<n;i++){//每一行的头和尾部
if(grid[i][0]==1) DFS(grid,i,0);
if(grid[i][m-1]==1) DFS(grid,i,m-1);
}
for(int i=0;i<m;i++){
if(grid[0][i]==1) DFS(grid,0,i);
if(grid[n-1][i]==1) DFS(grid,n-1,i);
}
count=0;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(grid[i][j]==1){
DFS(grid,i,j);
}
}
}
std::cout << count << std::endl;
}
//广搜
cpp
//广搜
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
int dir[4][2]={1,0,0,1,-1,0,0,-1};
int count;
void BFS(vector<vector<int>>& grid,int x,int y){
queue<pair<int,int>> que;
que.push({x,y});
grid[x][y]=0;//把陆地变成海洋
count++;
while(!que.empty()){
pair<int ,int> cur = que.front(); que.pop();
int curx=cur.first;
int cury=cur.second;
for(int i=0;i<4;i++){
int nextX=curx+dir[i][0];
int nextY=cury+dir[i][1];
if(nextX<0||nextX>=grid.size()||nextY<0||nextY>=grid[0].size()) continue;
if (grid[nextX][nextY] == 1){
que.push({nextX,nextY});
count++;
grid[nextX][nextY]=0;
}
}
}
return;
}
int main(){
int n,m;
cin>>n>>m;
vector<vector<int>> grid(n,vector<int>(m,0));
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
cin>>grid[i][j];
}
}
for(int i=0;i<n;i++){//每一行的头和尾部
if(grid[i][0]==1) BFS(grid,i,0);
if(grid[i][m-1]==1) BFS(grid,i,m-1);
}
for(int i=0;i<m;i++){
if(grid[0][i]==1) BFS(grid,0,i);
if(grid[n-1][i]==1) BFS(grid,n-1,i);
}
count=0;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(grid[i][j]==1){
BFS(grid,i,j);
}
}
}
std::cout << count << std::endl;
}
题目:沉没孤岛
题目描述
给定一个由 1(陆地)和 0(水)组成的矩阵,岛屿指的是由水平或垂直方向上相邻的陆地单元格组成的区域,且完全被水域单元格包围。孤岛是那些位于矩阵内部、所有单元格都不接触边缘的岛屿。
现在你需要将所有孤岛"沉没",即将孤岛中的所有陆地单元格(1)转变为水域单元格(0)。
输入描述
第一行包含两个整数 N, M,表示矩阵的行数和列数。
之后 N 行,每行包含 M 个数字,数字为 1 或者 0,表示岛屿的单元格。
输出描述
输出将孤岛"沉没"之后的岛屿矩阵。 注意:每个元素后面都有一个空格
题目分析:
上一题中我们知道了怎么去寻找孤岛,那么找到之后把他变成0不久好了吗?不过上一题中我们把陆地变成海洋,现在不行了,因为陆地时不能变得。那么我们在遍历的时候把这些陆地给标记一下,孤岛沉没之后变回来就好了。这个标记呢,可以使用二维数组,也可以把1变成2,能和海洋区分就可以。
//深搜
cpp
#include<iostream>
#include<vector>
using namespace std;
int dir[4][2]={1,0,0,1,-1,0,0,-1};
void DFS(vector<vector<int>>& grid,int x,int y){
grid[x][y]=2;//把陆地变成海洋
for(int i=0;i<4;i++){
int nextX=x+dir[i][0];
int nextY=y+dir[i][1];
if(nextX<0||nextX>=grid.size()||nextY<0||nextY>=grid[0].size()) continue;
if (grid[nextX][nextY] == 0||grid[nextX][nextY]==2) continue;
DFS(grid,nextX,nextY);
}
return;
}
int main(){
int n,m;
cin>>n>>m;
vector<vector<int>> grid(n,vector<int>(m,0));
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
cin>>grid[i][j];
}
}
for(int i=0;i<n;i++){//每一行的头和尾部
if(grid[i][0]==1) DFS(grid,i,0);
if(grid[i][m-1]==1) DFS(grid,i,m-1);
}
for(int i=0;i<m;i++){
if(grid[0][i]==1) DFS(grid,0,i);
if(grid[n-1][i]==1) DFS(grid,n-1,i);
}
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(grid[i][j]==1){
grid[i][j]=0;
}
if(grid[i][j]==2){
grid[i][j]=1;
}
}
}
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
std::cout << grid[i][j] << " ";
}
std::cout << std::endl;
}
}
//广搜
cpp
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
int dir[4][2]={1,0,0,1,-1,0,0,-1};
int count;
void BFS(vector<vector<int>>& grid,int x,int y){
queue<pair<int,int>> que;
que.push({x,y});
grid[x][y]=2;//把陆地变成海洋
count++;
while(!que.empty()){
pair<int ,int> cur = que.front(); que.pop();
int curx=cur.first;
int cury=cur.second;
for(int i=0;i<4;i++){
int nextX=curx+dir[i][0];
int nextY=cury+dir[i][1];
if(nextX<0||nextX>=grid.size()||nextY<0||nextY>=grid[0].size()) continue;
if (grid[nextX][nextY] == 1){
que.push({nextX,nextY});
count++;
grid[nextX][nextY]=2;
}
}
}
return;
}
int main(){
int n,m;
cin>>n>>m;
vector<vector<int>> grid(n,vector<int>(m,0));
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
cin>>grid[i][j];
}
}
for(int i=0;i<n;i++){//每一行的头和尾部
if(grid[i][0]==1) BFS(grid,i,0);
if(grid[i][m-1]==1) BFS(grid,i,m-1);
}
for(int i=0;i<m;i++){
if(grid[0][i]==1) BFS(grid,0,i);
if(grid[n-1][i]==1) BFS(grid,n-1,i);
}
count=0;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(grid[i][j]==1){
grid[i][j]=0;//孤岛变为0
}
if(grid[i][j]==2){
grid[i][j]=1;//陆地还原
}
}
}
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
std::cout << grid[i][j]<<" ";
}
std::cout <<std::endl;
}
}
题目:水流问题
题目描述
现有一个 N × M 的矩阵,每个单元格包含一个数值,这个数值代表该位置的相对高度。矩阵的左边界和上边界被认为是第一组边界,而矩阵的右边界和下边界被视为第二组边界。
矩阵模拟了一个地形,当雨水落在上面时,水会根据地形的倾斜向低处流动,但只能从较高或等高的地点流向较低或等高并且相邻(上下左右方向)的地点。我们的目标是确定那些单元格,从这些单元格出发的水可以达到第一组边界和第二组边界。
输入描述
第一行包含两个整数 N 和 M,分别表示矩阵的行数和列数。
后续 N 行,每行包含 M 个整数,表示矩阵中的每个单元格的高度。
输出描述
输出共有多行,每行输出两个整数,用一个空格隔开,表示可达第一组边界和第二组边界的单元格的坐标,输出顺序任意。
题目分析:
判断高出的水能不能流到边界。一种就是直接去搜索能否到达边界,另外一种,反过来,边界出逆流而上能到达哪些区域。下面的题我使用了第二种方式。
深搜
cpp
#include<iostream>
#include<vector>
using namespace std;
int dir[4][2]={1,0,0,1,-1,0,0,-1};
void DFS(vector<vector<int>>& grid,vector<vector<bool>>& visited,int x,int y){
if(visited[x][y]) return;
visited[x][y]=true;
for(int i=0;i<4;i++){
int nextX=x+dir[i][0];
int nextY=y+dir[i][1];
if(nextX<0||nextX>=grid.size()||nextY<0||nextY>=grid[0].size()) continue;
if(grid[x][y]>grid[nextX][nextY]) continue;
DFS(grid,visited,nextX,nextY);
}
return;
}
int main()
{
int n,m;
cin>>n>>m;
vector<vector<int>> grid(n,vector<int>(m,0));
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
cin>>grid[i][j];
}
}
// 标记从第一组边界上的节点出发,可以遍历的节点
vector<vector<bool>> firstBorder(n, vector<bool>(m, false));
// 标记从第一组边界上的节点出发,可以遍历的节点
vector<vector<bool>> secondBorder(n, vector<bool>(m, false));
for(int i=0;i<n;i++){
DFS(grid,firstBorder,i,0);
DFS(grid,secondBorder,i,m-1);
}
for(int j=0;j<m;j++){
DFS(grid,firstBorder,0,j);
DFS(grid,secondBorder,n-1,j);
}
for(int i=0;i<n;i++)
for(int j=0;j<m;j++){
// 如果这个节点,从第一组边界和第二组边界出发都遍历过,就是结果
if (firstBorder[i][j] && secondBorder[i][j]) cout << i << " " << j << endl;
}
}
题目:建造最大的岛屿
题目描述
给定一个由 1(陆地)和 0(水)组成的矩阵,你最多可以将矩阵中的一格水变为一块陆地,在执行了此操作之后,矩阵中最大的岛屿面积是多少。
岛屿面积的计算方式为组成岛屿的陆地的总数。岛屿是被水包围,并且通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设矩阵外均被水包围。
输入描述
第一行包含两个整数 N, M,表示矩阵的行数和列数。之后 N 行,每行包含 M 个数字,数字为 1 或者 0,表示岛屿的单元格。
输出描述
输出一个整数,表示最大的岛屿面积。
题目分析:
把海洋变成陆地,让岛屿面积最大。最直观的解法就是遍历每一个海洋区域,把他变成陆地后,搜索最大的陆地面积。但是这样的搜索会让我重复大量的无用操作,这个操作主要在搜索岛屿的问题上,那么减少搜索就可以完成优化。
首先把所有的陆地面积都记录下来,并且编号,等到遍历海洋区域的时候,我们直接加上这一部分的面积,就可以了,避免了重复搜索。
//深搜
cpp
#include<iostream>
#include<vector>
#include <unordered_set>
#include <unordered_map>
using namespace std;
int n,m;
int dir[4][2]={1,0,0,1,-1,0,0,-1};
int count;
void DFS(vector<vector<int>>& gird,vector<vector<bool>>& visited,int x,int y,int mark){
if(gird[x][y]==0||visited[x][y]) return;
gird[x][y]=mark;//定义编号
visited[x][y]=true;
count++;
for(int i=0;i<4;i++){
int nextx=x+dir[i][0];
int nexty=y+dir[i][1];
if(nextx<0||nextx>=n||nexty<0||nexty>=m) continue;
DFS(gird,visited,nextx,nexty,mark);
}
}
int main(){
cin>>n>>m;
vector<vector<int>> gird(n,vector<int>(m,0));
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
cin>>gird[i][j];
}
}
//标记那些格子访问过
vector<vector<bool>> visited(n,vector<bool>(m,false));
unordered_map<int ,int> gridNum; //记录陆地大小
int mark = 2; // 记录每个岛屿的编号
bool isAllGrid = true; // 标记是否整个地图都是陆地
//先统计每快陆地的大小
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(gird[i][j]==0) isAllGrid=false;
if(gird[i][j]==1&&!visited[i][j]){
count=0;
DFS(gird,visited,i,j,mark);
gridNum[mark]=count;//记录大小
mark++;
}
}
}
if(isAllGrid){
cout<<n*m<<endl;
return 0;
}
// 以下逻辑是根据添加陆地的位置,计算周边岛屿面积之和
int result = 0; // 记录最后结果
unordered_set<int> visitedGrid; // 标记访问过的岛屿
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
visitedGrid.clear();
count = 1; // 记录连接之后的岛屿数量
if(gird[i][j]==0){
for(int k=0;k<4;k++){
int neari = i + dir[k][1]; // 计算相邻坐标
int nearj = j + dir[k][0];
if (neari < 0 || neari >= n || nearj < 0 || nearj >= m) continue;
//count查找是否存在
if(visitedGrid.count(gird[neari][nearj])) continue;//添加过的岛屿不添加
// 把相邻四面的岛屿数量加起来
count += gridNum[gird[neari][nearj]];
visitedGrid.insert(gird[neari][nearj]); // 标记该岛屿已经添加过
}
}
result=max(result,count);
}
}
cout<<result<<endl;
}
题目:字符串接龙
题目描述
字典 strList 中从字符串 beginStr 和 endStr 的转换序列是一个按下述规格形成的序列:
-
序列中第一个字符串是 beginStr。
-
序列中最后一个字符串是 endStr。
-
每次转换只能改变一个字符。
-
转换过程中的中间字符串必须是字典 strList 中的字符串,且strList里的每个字符串只用使用一次。
给你两个字符串 beginStr 和 endStr 和一个字典 strList,找到从 beginStr 到 endStr 的最短转换序列中的字符串数目。如果不存在这样的转换序列,返回 0。
输入描述
第一行包含一个整数 N,表示字典 strList 中的字符串数量。 第二行包含两个字符串,用空格隔开,分别代表 beginStr 和 endStr。 后续 N 行,每行一个字符串,代表 strList 中的字符串。
输出描述
输出一个整数,代表从 beginStr 转换到 endStr 需要的最短转换序列中的字符串数量。如果不存在这样的转换序列,则输出 0。
输入示例
6
abc def
efc
dbc
ebc
dec
dfc
yhn
输出示例
4
提示信息
从 startStr 到 endStr,在 strList 中最短的路径为 abc -> dbc -> dec -> def,所以输出结果为 4,如图:
数据范围:
2 <= N <= 500
题目分析:
实际上就是一个求最短路径,关键在于我们如何去找下一个结点。显然,将原来的字符串的字符改变就可以得到下一个结点了,而这个新的字符串如果在我们的字典中,那就意味着可行。本题使用广搜即可,深搜的话很麻烦,需要处理回溯。
cpp
#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>
#include <unordered_map>
#include <queue>
using namespace std;
int main(){
int n;
string beginStr,endStr,str;
cin>>n;
cin>>beginStr>>endStr;
unordered_set<string> strSet;
for(int i=0;i<n;i++){
cin >> str;
strSet.insert(str);
}
// 记录strSet里的字符串是否被访问过,同时记录路径长度
unordered_map<string, int> visitMap; // <记录的字符串,路径长度>
queue<string> que;
que.push(beginStr);
visitMap.insert(pair<string,int>(beginStr,1));//初始化
while(!que.empty()){
string word=que.front();
que.pop();
int path=visitMap[word];//路径长度
//开始替换每一个字符
for(int i=0;i<word.size();i++){
string newWord=word;
//26个字母替换
for(int j=0;j<26;j++){
newWord[i]=j+'a';
if(newWord==endStr){//找到终点
std::cout << path +1<< std::endl;
return 0;
}
// 字符串集合里出现了newWord,并且newWord没有被访问过
if (strSet.find(newWord) != strSet.end()
&& visitMap.find(newWord) == visitMap.end()) {
// 添加访问信息,并将新字符串放到队列中
visitMap.insert(pair<string, int>(newWord, path + 1));
que.push(newWord);
}
}
}
}
cout<<0<<endl;
}
题目:有向图的完全可达性
题目描述
给定一个有向图,包含 N 个节点,节点编号分别为 1,2,...,N。现从 1 号节点开始,如果可以从 1 号节点的边可以到达任何节点,则输出 1,否则输出 -1。
输入描述
第一行包含两个正整数,表示节点数量 N 和边的数量 K。 后续 K 行,每行两个正整数 s 和 t,表示从 s 节点有一条边单向连接到 t 节点。
输出描述
如果可以从 1 号节点的边可以到达任何节点,则输出 1,否则输出 -1。
题目分析:
因为这里使用的时acm模式,需要确认的时,有向图的存储我们使用邻接表。
思路其实不复杂,我们已经知道了广搜和深搜,那么搜索过的结点标记上,最后如果没有未标记的结点即可完全可达。还是使用广搜的解法。
cpp
#include<iostream>
#include<vector>
#include <list>
using namespace std;
//key 当前遍历的结点
void dfs(vector<list<int>>& graph,int key,vector<bool>& visited){
if(visited[key]) return;
visited[key]=true;
list<int> keys=graph[key];
for(int key:keys){
dfs(graph,key,visited);
}
}
int main(){
int n,m,s,t;
cin>>n>>m;
//邻接表
vector<list<int>> graph(n+1);
while(m--){
cin>>s>>t;
graph[s].push_back(t);
}
vector<bool> visited(n+1,false);
dfs(graph,1,visited);
for(int i=1;i<visited.size();i++){
if(visited[i]==false){
cout<<-1<<endl;
return 0;
}
}
cout<<1<<endl;
}
题目:岛屿的周长
题目描述
给定一个由 1(陆地)和 0(水)组成的矩阵,岛屿是被水包围,并且通过水平方向或垂直方向上相邻的陆地连接而成的。
你可以假设矩阵外均被水包围。在矩阵中恰好拥有一个岛屿,假设组成岛屿的陆地边长都为 1,请计算岛屿的周长。岛屿内部没有水域。
输入描述
第一行包含两个整数 N, M,表示矩阵的行数和列数。之后 N 行,每行包含 M 个数字,数字为 1 或者 0,表示岛屿的单元格。
输出描述
输出一个整数,表示岛屿的周长。
题目分析:
又是岛屿问题。深搜还是广搜?但是这一题有必要吗?没有必要,求周长而已,遍历所有点,陆地区域就找四个方向为海洋区域,周长就增加。
cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> grid(n, vector<int>(m, 0));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> grid[i][j];
}
}
int direction[4][2] = {0, 1, 1, 0, -1, 0, 0, -1};
int result = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == 1) {
for (int k = 0; k < 4; k++) { // 上下左右四个方向
int x = i + direction[k][0];
int y = j + direction[k][1]; // 计算周边坐标x,y
if (x < 0 // x在边界上
|| x >= grid.size() // x在边界上
|| y < 0 // y在边界上
|| y >= grid[0].size() // y在边界上
|| grid[x][y] == 0) { // x,y位置是水域
result++;
}
}
}
}
}
cout << result << endl;
}
对于更详细的解析与其他语言的代码块,可以去代码随想录上查看。