- 101. 孤島的總面積
- DFS思路
- BFS思路
- 102. 沉沒孤島
- 103. 水流問題
- 104. 建造最大島嶼
101. 孤島的總面積
題目連結:https://kamacoder.com/problempage.php?pid=1173
文章講解:https://programmercarl.com/kamacoder/0101.孤島的總面積.html
題目狀態:看題解
DFS思路
思路:
程式碼結構
-
變數和方向陣列:
dir[4][2]
:定義了四個方向的移動(上、左、下、右)。count
:用於統計符合條件的陸地塊數量。
-
深度優先搜尋函式
dfs
:- 輸入引數為二維陣列
grid
和當前座標(x, y)
。 - 將當前座標的值設為 0(表示已訪問)。
- 增加
count
計數。 - 遍歷四個方向,遞迴呼叫
dfs
,前提是新座標在邊界內且值為 1(表示陸地)。
- 輸入引數為二維陣列
-
主函式
main
:- 讀取網格的大小
n
和m
。 - 初始化一個
n x m
的二維陣列grid
。 - 從輸入讀取網格資料。
- 從網格的四個邊界開始,使用
dfs
清除邊界連線的陸地。 - 重置
count
。 - 遍歷內部陸地,使用
dfs
統計不與邊界相連的陸地塊。 - 輸出
count
。
- 讀取網格的大小
邏輯分析
-
邊界處理:
- 先從網格的邊界開始,使用
dfs
清除與邊界相連的陸地。這樣可以排除掉與邊界相連的陸地塊。
- 先從網格的邊界開始,使用
-
統計孤立陸地:
- 遍歷整個網格,使用
dfs
統計不與邊界相連的陸地塊。
- 遍歷整個網格,使用
程式碼:
#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 j = 0; j < m; ++j) {
if(grid[0][j] == 1) dfs(grid, 0, j);
if(grid[n - 1][j] == 1) dfs(grid, n - 1, j);
}
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);
}
}
cout << count << endl;
}
BFS思路
思路:
程式碼結構
-
方向陣列
dir
:- 表示四個方向(右、下、左、上)的移動。
dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}
表示:- 右:(0, 1)
- 下:(1, 0)
- 左:(-1, 0)
- 上:(0, -1)
-
全域性變數
count
:- 用於統計符合條件的陸地格子數量。
-
廣度優先搜尋函式
bfs
:- 引數:網格
grid
,起始座標(x, y)
。 - 使用佇列
que
來實現BFS。 - 將起始點標記為訪問過(設定為0)並加入佇列。
- 遍歷佇列中的每個點,向四個方向擴充套件。
- 檢查邊界條件,確保不越界。
- 對於相鄰的陸地格子,標記為訪問過並加入佇列。
- 引數:網格
-
主函式
main
:- 輸入:網格大小
n
和m
,以及網格資料。 - 初始化網格
grid
。 - 從網格的邊界開始,移除與邊界相連的陸地。
- 重置
count
。 - 遍歷整個網格,統計被包圍的陸地數量。
- 輸出結果。
- 輸入:網格大小
邏輯流程
-
輸入處理:
- 讀取網格的大小
n
和m
。 - 讀取網格資料,填充
grid
。
- 讀取網格的大小
-
邊界BFS:
- 從網格的四個邊界開始,使用
bfs
移除與邊界相連的陸地。 - 這部分陸地不計入最終統計,因為題目可能要求只統計被完全包圍的陸地。
- 從網格的四個邊界開始,使用
-
統計被包圍的陸地:
- 重置
count
。 - 遍歷網格內部,使用
bfs
統計所有剩餘陸地格子的數量。
- 重置
-
輸出結果:
- 列印出符合條件的陸地格子數量。
程式碼:
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 儲存四個方向
int count = 0; // 統計符合題目要求的陸地空格數量
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.size()) continue; // 越界了,直接跳過
if(grid[nextx][nexty] == 1) {
que.push({nextx, nexty});
count++;
grid[nextx][nexty] = 0; // 只要加入佇列立刻標記
}
}
}
}
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 j = 0; j < m; ++j) {
if(grid[0][j] == 1) bfs(grid, 0, j);
if(grid[n - 1][j] == 1) bfs(grid, n - 1, j);
}
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);
}
}
cout << count << endl;
}
102. 沉沒孤島
題目連結:https://kamacoder.com/problempage.php?pid=1174
文章講解:https://programmercarl.com/kamacoder/0102.沉沒孤島.html
題目狀態:看題解
思路:
程式碼結構
-
方向陣列
dir
:- 定義了四個方向(上、左、下、右)的移動座標:
{-1, 0}
、{0, -1}
、{1, 0}
、{0, 1}
。
- 定義了四個方向(上、左、下、右)的移動座標:
-
DFS 函式:
- 引數: 接受一個二維陣列
grid
和當前座標(x, y)
。 - 功能: 將當前陸地標記為 2,並遞迴遍歷四個方向的相鄰陸地。
- 邊界條件: 檢查是否越界或遇到水(0)或已訪問(2)的格子。
- 引數: 接受一個二維陣列
-
主函式
main
:- 輸入: 從標準輸入讀取網格大小
n
和m
,以及網格資料。 - 步驟一: 從邊界開始,將與邊界相連的陸地(1)透過 DFS 標記為 2。
- 步驟二: 將剩餘的陸地(孤島)變成水(0)。
- 步驟三: 將標記為 2 的邊界陸地恢復為陸地(1)。
- 輸出: 列印修改後的網格。
- 輸入: 從標準輸入讀取網格大小
邏輯流程
-
邊界處理:
- 從網格的四條邊界開始,使用 DFS 將所有與邊界相連的陸地標記為 2。
-
孤島處理:
- 遍歷整個網格,將所有未標記為 2 的陸地(即孤島)變為水(0)。
-
恢復邊界陸地:
- 將標記為 2 的格子恢復為陸地(1),因為它們與邊界相連。
程式碼:
#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];
}
}
// 步驟一:(將邊界陸地1變為2)
// 從左邊和右邊向中間遍歷
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 j = 0; j < m; ++j) {
if(grid[0][j] == 1) dfs(grid, 0, j);
if(grid[n - 1][j] == 1) dfs(grid, n - 1, j);
}
// 步驟二:將地圖中的1(孤島)變為0
// 步驟三:將地圖中的2(邊界陸地)變為1
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) {
cout << grid[i][j] << " ";
}
cout << endl;
}
}
103. 水流問題
題目連結:https://kamacoder.com/problempage.php?pid=1175
文章講解:https://programmercarl.com/kamacoder/0103.水流問題.html
題目狀態:看題解
思路:
透過便利在給定的網格中找到能夠同時到達兩組邊界的點。透過深度優先搜尋,計算每個點可以訪問的區域,並檢查這些區域是否滿足到達邊界的條件。
-
變數宣告
n, m
: 網格的行數和列數。dir
: 四個方向的移動向量,分別表示上、左、下、右。
-
函式
dfs
- 引數:
grid
(網格)、visited
(記錄訪問狀態的二維布林陣列)、x
、y
(當前座標)。 - 功能:從
(x, y)
開始,遞迴地訪問所有可以到達的點。 - 檢查條件:
- 如果當前點已經訪問過,直接返回。
- 確保下一個點在網格範圍內。
- 確保移動方向的高度不增加(即只能走向相等或更低的高度)。
- 引數:
-
函式
isResult
- 引數:
grid
(網格)、x
、y
(起始座標)。 - 功能:檢查從
(x, y)
出發是否可以同時到達第一組和第二組邊界。 - 使用
dfs
標記從(x, y)
可以到達的所有點。 - 檢查是否可以到達:
- 第一組邊界(上邊界和左邊界)。
- 第二組邊界(右邊界和下邊界)。
- 返回
true
如果可以同時到達兩組邊界,否則返回false
。
- 引數:
-
main
函式- 讀取網格的大小
n
和m
。 - 讀取網格的高度資訊。
- 遍歷每個點
(i, j)
,呼叫isResult
檢查是否滿足條件。 - 如果滿足條件,輸出該點的座標。
- 讀取網格的大小
程式碼:
#include <iostream>
#include <vector>
using namespace std;
int n, m;
int dir[4][2] = {-1, 0, 0, -1, 1, 0, 0, 1};
// 從x,y出發把可以走的地方都標記上
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 >= n || nexty < 0 || nexty >= m) continue;
if(grid[x][y] < grid[nextx][nexty]) continue; // 高度不合適
dfs(grid, visited, nextx, nexty);
}
return;
}
bool isResult(vector<vector<int>> &grid, int x, int y) {
vector<vector<bool>> visited(n, vector<bool>(m, false));
// 深搜,將x,y出發能到的節點都標記上
dfs(grid, visited, x, y);
bool isFirst = false;
bool isSecond = false;
// 以下就是判斷x,y出發,是否到達第一組邊界和第二組邊界
// 第一邊界的上邊
for(int j = 0; j < m; ++j) {
if(visited[0][j]) {
isFirst = true;
break;
}
}
// 第一邊界的左邊
for(int i = 0; i < n; ++i) {
if(visited[i][0]) {
isFirst = true;
break;
}
}
// 第二邊界的右邊
for(int j = 0; j < m; ++j) {
if(visited[n - 1][j]) {
isSecond = true;
break;
}
}
// 第二邊界下邊
for(int i = 0; i < n; ++i) {
if(visited[i][m - 1]) {
isSecond = true;
break;
}
}
if(isFirst && isSecond) return true;
return false;
}
int main() {
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) {
for(int j = 0; j < m; ++j) {
if(isResult(grid, i, j)) cout << i << " " << j << endl;
}
}
}
上述程式碼和思路在卡碼網中執行會出現超時情況,下面是最佳化的思路和程式碼。
思路:
不進行單個元素的遍歷,而是從邊界向中間遍歷。
程式碼:
#include <iostream>
#include <vector>
using namespace std;
int n, m;
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 >= n || nexty < 0 || nexty >= m) continue;
if (grid[x][y] > grid[nextx][nexty]) continue; // 注意:這裡是從低向高遍歷
dfs (grid, visited, nextx, nexty);
}
return;
}
int main() {
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;;
}
}
}
104. 建造最大島嶼
題目連結:https://kamacoder.com/problempage.php?pid=1176
文章講解:https://programmercarl.com/kamacoder/0104.建造最大島嶼.html
題目狀態:看題解,好難
思路:
找出透過將一個海水格子變為陸地後,可能形成的最大島嶼面積。透過標記和記錄每個島嶼的面積,高效地計算出將某個海水格子變為陸地後的最大可能面積。
-
變數宣告
n, m
: 網格的行數和列數。count
: 當前島嶼的面積。dir
: 四個方向的移動向量,分別表示右、下、左、上。
-
函式
dfs
- 引數:
grid
(網格)、visited
(記錄訪問狀態的二維布林陣列)、x
、y
(當前座標)、mark
(島嶼標記)。 - 功能:透過深度優先搜尋,將所有連線的陸地標記為同一個島嶼。
- 終止條件:當前點已經訪問過或是海水。
- 標記當前陸地並遞迴訪問相鄰的陸地。
- 引數:
-
main
函式- 讀取網格的大小
n
和m
。 - 讀取網格的高度資訊。
- 初始化
visited
陣列和gridNum
對映,用於儲存每個島嶼的面積。 - 遍歷網格,使用
dfs
查詢並標記所有島嶼,記錄每個島嶼的面積。 - 檢查是否整個網格都是陸地,如果是,直接輸出總面積。
- 讀取網格的大小
-
計算最大可能的島嶼面積
- 遍歷每個海水格子
(i, j)
,嘗試將其變為陸地。 - 對於每個海水格子,檢查其四個相鄰格子。
- 計算相鄰島嶼的總面積(避免重複計算同一島嶼)。
- 更新最大島嶼面積。
- 遍歷每個海水格子
程式碼:
#include <iostream>
#include <vector>
#include <unordered_set>
#include <unordered_map>
using namespace std;
int n, m;
int count;
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四個方向
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y, int mark) {
if (visited[x][y] || grid[x][y] == 0) return; // 終止條件:訪問過的節點 或者 遇到海水
visited[x][y] = true; // 標記訪問過
grid[x][y] = mark; // 給陸地標記新標籤
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(grid, visited, nextx, nexty, mark);
}
}
int main() {
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>> 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 (grid[i][j] == 0) isAllGrid = false;
if (!visited[i][j] && grid[i][j] == 1) {
count = 0;
dfs(grid, visited, i, j, mark); // 將與其連結的陸地都標記上 true
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++) {
count = 1; // 記錄連線之後的島嶼數量
visitedGrid.clear(); // 每次使用時,清空
if (grid[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;
if (visitedGrid.count(grid[neari][nearj])) continue; // 新增過的島嶼不要重複新增
// 把相鄰四面的島嶼數量加起來
count += gridNum[grid[neari][nearj]];
visitedGrid.insert(grid[neari][nearj]); // 標記該島嶼已經新增過
}
}
result = max(result, count);
}
}
cout << result << endl;
}