目录
一、沉默孤岛(广搜)
原题卡码102 102. 沉没孤岛
思路:先遍历四周,把陆地改为2;再遍历一遍整图,把孤岛改为0,同时把2改回1
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int dirs[4][2] = { {0,1},{1,0},{0,-1},{-1,0} };
void bfs(vector<vector<int>>& graph, int x, int y, int n, int m)
{
queue<pair<int, int>> que;
que.push({ x,y });
graph[x][y] = 2;
while (!que.empty())
{
pair<int, int> curNode = que.front();
que.pop();
for (const auto& dir : dirs)
{
int nextX = curNode.first + dir[0];
int nextY = curNode.second + dir[1];
if (nextX >= n || nextX < 0 || nextY >= m || nextY < 0)
{
continue;
}
if (graph[nextX][nextY] == 0 || graph[nextX][nextY] == 2)
{
continue;
}
que.push({ nextX,nextY });
graph[nextX][nextY] = 2;
}
}
}
int main()
{
int N, M;
cin >> N >> M;
vector<vector<int>> graph(N, vector<int>(M, 0));
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
cin >> graph[i][j];
}
}
for (int i = 0; i < N; i++)
{
if (graph[i][0] == 1)
{
bfs(graph, i, 0, N, M);
}
if (graph[i][M - 1] == 1)
{
bfs(graph, i, M - 1, N, M);
}
}
for (int j = 0; j < M; j++)
{
if (graph[0][j] == 1)
{
bfs(graph, 0, j, N, M);
}
if (graph[N - 1][j] == 1)
{
bfs(graph, N - 1, j, N, M);
}
}
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
if (graph[i][j] == 1)
{
graph[i][j] = 0;
}
if (graph[i][j] == 2)
{
graph[i][j] = 1;
}
}
}
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
cout << graph[i][j] << " ";
}
cout << endl;
}
return 0;
}
二、太平洋大西洋水流问题
原题力扣417 . - 力扣(LeetCode)
1、暴力深搜
思路是对每一个点执行一次深搜,得到一个 visited 数组结果,再判断这个结果是否符合要求
时间复杂度 O(N^2*M^2),超时
int dirs[4][2] = { {0, 1}, {1, 0}, {0, -1}, {-1, 0} };
void dfs(vector<vector<int>>& heights, vector<vector<bool>>& visited, int x, int y, int n, int m)
{
visited[x][y] = true;
for (const auto& dir : dirs)
{
int nextX = x + dir[0];
int nextY = y + dir[1];
if (nextX >= n || nextX < 0 || nextY >= m || nextY < 0)
{
continue;
}
if (visited[nextX][nextY] || heights[nextX][nextY] > heights[x][y])
{
continue;
}
dfs(heights, visited, nextX, nextY, n, m);
}
}
bool isResult(vector<vector<bool>>& visited, int n, int m)
{
bool pacific = false;
bool atlantic = false;
for (int i = 0; i < n; i++)
{
if (visited[i][0])
{
pacific = true;
break;
}
}
for (int i = 0; i < n; i++)
{
if (visited[i][m - 1])
{
atlantic = true;
break;
}
}
for (int j = 0; j < m; j++)
{
if (pacific)
{
break;
}
if (visited[0][j])
{
pacific = true;
break;
}
}
for (int j = 0; j < m; j++)
{
if (atlantic)
{
break;
}
if (visited[n - 1][j])
{
atlantic = true;
break;
}
}
return pacific && atlantic;
}
vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights)
{
int n = heights.size();
int m = heights[0].size();
vector<vector<int>> result;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
vector<vector<bool>> visited(n, vector<bool>(m, false));
dfs(heights, visited, i, j, n, m);
if (isResult(visited, n, m))
{
result.push_back({ i, j });
}
}
}
return result;
}
2、逆流而上
优化思路,分别从太平洋和大西洋逆流而上,只有两个大洋同时到达的地块才是要找的结果。这样的好处是不需要重复建立和修改 visited 数组,而是一次遍历,全程有效。优化了时间复杂度,为O(N*M)
int dirs[4][2] = { {0, 1}, {1, 0}, {0, -1}, {-1, 0} };
void dfs(vector<vector<int>>& heights, vector<vector<bool>>& visited, int x, int y, int n, int m)
{
if (visited[x][y])
{
return;
}
visited[x][y] = true;
for (const auto& dir : dirs)
{
int nextX = x + dir[0];
int nextY = y + dir[1];
if (nextX >= n || nextX < 0 || nextY >= m || nextY < 0)
{
continue;
}
if (heights[nextX][nextY] < heights[x][y]) // 要逆流而上
{
continue;
}
dfs(heights, visited, nextX, nextY, n, m);
}
}
vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights)
{
int n = heights.size();
int m = heights[0].size();
vector<vector<bool>> pacific(n, vector<bool>(m, false)); // 从太平洋出发
vector<vector<bool>> atlantic(n, vector<bool>(m, false)); // 从大西洋出发
for (int i = 0; i < n; i++)
{
dfs(heights, atlantic, i, 0, n, m);
dfs(heights, atlantic, i, m - 1, n, m);
}
for (int j = 0; j < m; j++)
{
dfs(heights, pacific, 0, j, n, m);
dfs(heights, pacific, n - 1, j, n, m);
}
vector<vector<int>> result;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if (pacific[i][j] && atlantic[i][j]) // 只有太平洋和大西洋同时能到的,才加入结果
{
result.push_back({ i, j });
}
}
}
return result;
}
三、最大人工岛(标记地图,深搜)
原题力扣827 . - 力扣(LeetCode)
主要思路为先遍历地图,记录每个岛屿各自的面积;
再修改地图中的海洋格并比较修改后的最大面积
int count; // 记录每个岛屿的面积
int dirs[4][2] = { {0, 1}, {1, 0}, {0, -1}, {-1, 0} };
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y, int n, int m, int mark)
{
if (visited[x][y] || grid[x][y] == 0)
{
return;
}
visited[x][y] = true;
grid[x][y] = mark; // 标记岛屿编号
count++;
for (const auto& dir : dirs)
{
int nextX = x + dir[0];
int nextY = y + dir[1];
if (nextX >= n || nextX < 0 || nextY >= m || nextY < 0)
{
continue;
}
dfs(grid, visited, nextX, nextY, n, m, mark);
}
}
int largestIsland(vector<vector<int>>& grid)
{
int n = grid.size();
int m = grid[0].size();
vector<vector<bool>> visited(n, vector<bool>(m, false));
unordered_map<int, int> areas; // 岛屿编号——面积对照表
int mark = 2; // 初始岛屿编号
bool isAllGrid = true; // 是否全是陆地
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if (grid[i][j] == 0)
{
isAllGrid = false;
}
if (!visited[i][j] && grid[i][j] == 1)
{
count = 0; // 新的岛屿,计数器重置
dfs(grid, visited, i, j, n, m, mark);
areas[mark] = count; // 记录岛屿面积
mark++; // 编号递增
}
}
}
if (isAllGrid) // 全是陆地,直接返回面积
{
return n * m;
}
int ans = 0;
unordered_set<int> st; // 记录访问过的岛屿
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
count = 1; // 重置面积记录
st.clear(); // 重置岛屿记录
if (grid[i][j] == 0) // 找到了海洋格
{
for (const auto& dir : dirs) // 计算四周是否是岛屿
{
int nextX = i + dir[0];
int nextY = j + dir[1];
if (nextX >= n || nextX < 0 || nextY >= m || nextY < 0)
{
continue;
}
if (st.count(grid[nextX][nextY])) // 已经找到的岛屿,跳过
{
continue;
}
count += areas[grid[nextX][nextY]]; // 面积加起来
st.insert(grid[nextX][nextY]); // 标记访问过的岛屿
}
}
ans = max(ans, count);
}
}
return ans;
}
四、单词接龙(广搜)
原题力扣127 . - 力扣(LeetCode)
广度优先搜索只要找到了终点,就必是最短路径
int ladderLength(string beginWord, string endWord, vector<string>& wordList)
{
unordered_set<string> st(wordList.begin(), wordList.end()); // 记录字典
if (st.find(endWord) == st.end())
{
return 0;
}
unordered_map<string, int> visited; // <访问过的字符串,路径长度>
queue<string> que;
que.push(beginWord);
visited[beginWord] = 1;
while (!que.empty())
{
string curWord = que.front();
que.pop();
int curLength = visited[curWord]; // 当前路径长度
// 依次用26个字母替换当前字符串,符合条件的入队
for (int i = 0; i < curWord.size(); i++)
{
string newWord = curWord;
for (int j = 0; j < 26; j++)
{
newWord[i] = j + 'a';
if (newWord == endWord)
{
return curLength + 1;
}
if (st.find(newWord) != st.end() && !visited.count(newWord))
{
que.push(newWord);
visited[newWord] = curLength + 1;
}
}
}
}
return visited[endWord];
}
五、有向图的完全可达性(深搜)
原题卡码105 105. 有向图的完全可达性
不需要回溯,因为不需要输出可行路径,只需要标记可达即可
vector<int> path;
void dfs(vector<list<int>>& graph, vector<bool>& visited, int cur)
{
if (visited[cur])
{
return;
}
visited[cur] = true;
for (const auto& next : graph[cur])
{
dfs(graph, visited, next);
}
}
int main()
{
int N, K;
cin >> N >> K;
vector<list<int>> graph(N + 1);
int s, t;
for (int i = 0; i < K; i++)
{
cin >> s >> t;
graph[s].push_back(t);
}
vector<bool> visited(N + 1, false);
dfs(graph, visited, 1);
for (int i = 1; i <= N; i++)
{
if (visited[i] == false)
{
cout << -1 << endl;
return 0;
}
}
cout << 1 << endl;
return 0;
}
六、岛屿的周长(避免惯性思维)
原题力扣463 . - 力扣(LeetCode)
不要一看到岛屿就采用深搜或者广搜,有些题简单分析计算即可
int islandPerimeter(vector<vector<int>>& grid)
{
int total = 0; // 总路地块数量
int cover = 0; // 相邻陆地数量
for (int i = 0; i < grid.size(); i++)
{
for (int j = 0; j < grid[0].size(); j++)
{
if (grid[i][j] == 1)
{
total++;
if (i - 1 >= 0 && grid[i - 1][j] == 1)
{
cover++;
}
if (j - 1 >= 0 && grid[i][j - 1] == 1)
{
cover++;
}
}
}
}
return total * 4 - cover * 2;
}