目錄
1. 島嶼數量
1.1 題目解析
1.2 解法
1.3 代碼實現
2.?島嶼的最大面積
2.1 題目解析
2.2 解法
2.3 代碼實現
1. 島嶼數量
https://leetcode.cn/problems/number-of-islands/
給你一個由?'1'
(陸地)和?'0'
(水)組成的的二維網格,請你計算網格中島嶼的數量。
島嶼總是被水包圍,并且每座島嶼只能由水平方向和/或豎直方向上相鄰的陸地連接形成。
此外,你可以假設該網格的四條邊均被水包圍。
示例 1:
輸入:grid = [['1','1','1','1','0'],['1','1','0','1','0'],['1','1','0','0','0'],['0','0','0','0','0'] ] 輸出:1
示例 2:
輸入:grid = [['1','1','0','0','0'],['1','1','0','0','0'],['0','0','1','0','0'],['0','0','0','1','1'] ] 輸出:3
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 300
grid[i][j]
?的值為?'0'
?或?'1'
1.1 題目解析
題目本質
-
在一個 m×n 的 0/1 網格上,統計由上下左右連通的“1”塊的個數;本質是“連通分量計數(四聯通)”。
常規解法
-
樸素想法:對每個為 ‘1’ 的格子啟動一次遍歷,把與之相連的 ‘1’ 都標記掉,然后計數 +1。遍歷可用 DFS 或 BFS。
問題分析
-
如果不做“訪問標記(vis)”,同一塊島嶼會被重復啟動遍歷 → 重復計數/超時。
-
如果標記時機不當(入隊后不立刻標記),同一坐標會被多次入隊 → 隊列暴漲。
-
邊界獲取不當(先取 grid[0].length 再判斷空矩陣)會 NPE。
-
復雜度:每格最多訪問一次,理想是 O(mn);只要保證“入隊(或遞歸)前標記”為時機,就不會退化。
思路轉折
-
要高效、且不重算:
-
必須維護 vis[m][n] 訪問標記;
-
必須把標記動作放在“入隊/入棧前”;
-
必須只在遇到“未訪問的 ‘1’”時啟動一次 BFS/DFS,并在啟動點處給計數器 +1。
-
-
這樣“每個格子至多被處理一次”,時間 O(mn)、空間 O(mn)。
1.2 解法
算法思想
-
遍歷整個網格;每當遇到 grid[i][j]=='1' && !vis[i][j]:
-
啟動一輪 BFS;
-
BFS 中用隊列彈出當前格,向四鄰擴展,對滿足邊界且為 ‘1’ 且未訪問的坐標:先標記 vis=true,再入隊;
-
該輪 BFS 結束后,說明整塊島嶼已被吞并,計數 ret++。
-
i)初始化
-
m = grid.length;若 m==0 直接返回 0。
-
n = grid[0].length;vis = new boolean[m][n];ret = 0。
ii)掃描網格
-
雙層循環 (i,j);當 grid[i][j]=='1' && !vis[i][j] 時,啟動 BFS(i,j)。
iii)BFS 過程
-
隊列入起點 (si,sj),立刻標記 vis[si][sj]=true。
-
不斷出隊 (a,b),向四方向 (a+dx[k], b+dy[k]) 擴展;滿足:
-
在邊界內、
-
未訪問、
-
且為 ‘1’
→ 先標記再入隊。 -
隊列清空即該島處理完畢,ret++。
-
iiii)返回 ret。
易錯點
-
n = grid[0].length 必須放在 m==0 判空之后,否則空矩陣會 NPE。
-
if (grid[i][j]=='1' && !vis[i][j]) 中的 !vis[i][j] 一開始當然都是 true,但第一輪 BFS 會把整塊島都標記為
true
,后續再遇到同島格子就不會重復啟動 BFS。 -
標記時機要在入隊前/時,否則同一坐標可能被多次入隊。
-
只統計“第一次發現這塊島”的那一刻(啟動 BFS 時)的一次 ret++
-
注意字符比較用
'1'
,不要寫成1
。 -
四方向遍歷下標不要寫錯。
-
不要重復無意義的標記(出隊后再次 vis[a][b]=true 屬于冗余)。
1.3 代碼實現
import java.util.*;class Solution {int[] dx = {0, 0, 1, -1};int[] dy = {1, -1, 0, 0};int m, n;Queue<int[]> q = new LinkedList<>();boolean[][] vis;int ret;public int numIslands(char[][] grid) {m = grid.length;if (m == 0) return 0; // 先判空,再取 nn = grid[0].length;vis = new boolean[m][n];ret = 0;for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if (grid[i][j] == '1' && !vis[i][j]) {bfs(grid, i, j); // 一次 BFS 吞并一整塊島}}}return ret;}// BFS:以 (si, sj) 為起點吞并整塊島嶼public void bfs(char[][] grid, int si, int sj) {q.clear(); // 成員隊列:顯式清空更穩妥q.offer(new int[]{si, sj});vis[si][sj] = true; // 入隊前/時標記,避免重復入隊while (!q.isEmpty()) {int[] cur = q.poll();int a = cur[0], b = cur[1];for (int k = 0; k < 4; k++) {int x = a + dx[k], y = b + dy[k];if (x >= 0 && x < m && y >= 0 && y < n&& !vis[x][y] && grid[x][y] == '1') {vis[x][y] = true; // 先標記,再入隊q.offer(new int[]{x, y});}}}ret++; // 本輪 BFS 結束 → 發現了一座島}
}
復雜度分析
-
時間復雜度:O(m·n),每個格子最多被入隊/出隊一次,四鄰檢查是常數。
-
空間復雜度:O(m·n),主要在 vis;隊列最壞可能裝下整層邊界上的若干格子,但受 O(mn) 上界。
2.?島嶼的最大面積
https://leetcode.cn/problems/max-area-of-island/
給你一個大小為?m x n
?的二進制矩陣?grid
?。
島嶼?是由一些相鄰的?1
?(代表土地) 構成的組合,這里的「相鄰」要求兩個?1
?必須在?水平或者豎直的四個方向上?相鄰。你可以假設?grid
?的四個邊緣都被?0
(代表水)包圍著。
島嶼的面積是島上值為?1
?的單元格的數目。
計算并返回?grid
?中最大的島嶼面積。如果沒有島嶼,則返回面積為?0
?。
示例 1:
輸入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]] 輸出:6 解釋:答案不應該是11
,因為島嶼只能包含水平或垂直這四個方向上的1
。
示例 2:
輸入:grid = [[0,0,0,0,0,0,0,0]] 輸出:0
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 50
grid[i][j]
?為?0
?或?1
2.1 題目解析
題目本質
-
在 m×n 的二值矩陣中,按四聯通把為 1 的格子聚合成若干連通分量,返回所有分量的最大節點數,也就是最大島嶼面積。
常規解法
-
線性掃描整張表,每當遇到未訪問的 1,就從該點出發做一次搜索,把整座島吞并,并統計面積。可用 BFS 或 DFS。
問題分析
-
如果不做訪問標記,同一島會被多次重復遍歷,時間爆炸。
-
如果標記時機晚(入隊后才標記,甚至出隊后才標記),同一坐標可能被多次入隊,導致隊列暴漲。
-
如果把面積計數放在“發現鄰居”時,會漏掉起點,得到 N?1 而不是 N。
-
判空順序不當會空指針,例如先取 grid[0].length 再判斷 m 是否為 0。
-
復雜度目標是 O(mn),只要保證每格最多被訪問一次即可。
思路轉折
-
要想穩定 O(mn):
-
需要全局 vis 數組去重。
-
需要在入隊之前標記 vis,保證每格最多入隊一次。
-
需要把面積計數與“訪問到一個新格子”綁定,最簡單是出隊時加一。
-
只在遇到 grid[i][j] == 1 且未訪問時啟動一次搜索,吞并整座島并更新答案。
-
2.2 解法
算法思想
-
雙層循環掃描網格,遇到未訪問的 1 啟動 BFS。
-
BFS 初始化把起點入隊并立刻標記已訪問。
-
循環彈出隊頭,面積加一,向四個方向擴展,把滿足邊界、為 1、未訪問的鄰居先標記后入隊。
-
本輪隊列清空即整座島處理完畢,用該島面積更新全局最大值。
i)初始化
-
m = grid.length,若 m == 0 直接返回 0。
-
n = grid[0].length,初始化 vis[m][n] 為 false。
-
準備一個整型答案 ret 保存最大面積。
ii)掃描
-
雙層循環 i, j。
-
當且僅當 grid[i][j] == 1 且 vis[i][j] 為 false 時,啟動 BFS(i, j)。
iii)BFS 細節
-
入隊起點并立刻標記 vis[si][sj] = true。
-
while 隊列非空:
-
彈出一個格子,面積加一。
-
四方向擴展,滿足邊界、為 1、未訪問的鄰居先標記再入隊。
-
-
返回本輪面積。
iiii)更新答案
-
ret = max(ret, 本輪面積)。
iiiii)返回 ret
易錯點
-
判空順序:必須先判斷 m 是否為 0,再讀取 grid[0].length。
-
標記時機:必須在入隊之前標記,防止同一坐標被多次入隊。
-
面積計數位置:推薦在出隊時加一,這樣每訪問一個格子恰好計一次;如果堅持在“發現鄰居”時計數,必須把起點先計入,否則會少一。
-
鄰居入隊坐標:入隊的是鄰居 (x, y),不要誤把起點 (si, sj) 重新塞回隊列。
-
只考慮四聯通,不能把對角線當作連通。
-
類型與比較:本題 grid 是 int[][],比較用 1 與 0;與 char[][] 的版本不要混用。
2.3 代碼實現
import java.util.*;class Solution {Queue<int[]> q = new LinkedList<>(); // 成員隊列,BFS 開始時清空更穩妥int m, n;boolean[][] vis;int[] dx = {0, 0, 1, -1};int[] dy = {1, -1, 0, 0};public int maxAreaOfIsland(int[][] grid) {m = grid.length;if (m == 0) return 0; // 先判空,避免 NPEn = grid[0].length;vis = new boolean[m][n];int ret = 0, cur = 0;for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if (grid[i][j] == 1 && !vis[i][j]) {cur = 0; // 可有可無,這里保持你的風格int tmp = bfs(grid, i, j, cur); // BFS 返回本島面積ret = Math.max(ret, tmp);}}}return ret;}// BFS 吞并以 (si, sj) 為起點的整座島,返回其面積public int bfs(int[][] grid, int si, int sj, int cur) {q.clear(); // 成員隊列,使用前先清空q.offer(new int[]{si, sj});vis[si][sj] = true;while (!q.isEmpty()) {int[] num = q.poll();int a = num[0], b = num[1];cur++; // 出隊即計數,保證每格計一次for (int i = 0; i < 4; i++) {int x = a + dx[i], y = b + dy[i];if (x >= 0 && x < m && y >= 0 && y < n&& grid[x][y] == 1 && !vis[x][y]) {vis[x][y] = true; // 入隊前標記q.offer(new int[]{x, y}); // 入隊鄰居坐標}}}return cur;}
}
復雜度分析
-
時間復雜度:O(mn),每個格子最多被訪問一次,四鄰檢查是常數。
-
空間復雜度:O(mn),主要為 vis;隊列最壞情況下也不超過 O(mn)。