1. 污染水域问题概述
污染水域是一个经典的算法问题,通常出现在编程竞赛和面试中。题目描述一片由网格表示的水域,其中某些区域被污染(标记为'1'),其他区域是干净的(标记为'0')。我们需要计算被污染区域的数量,或者找到最大的连续污染区域。
这个问题考察的核心能力包括:
- 二维数组的遍历技巧
- 深度优先搜索(DFS)或广度优先搜索(BFS)的应用
- 边界条件的处理
- 算法优化能力
2. 问题分析与解法思路
2.1 问题建模
我们可以将水域建模为一个m×n的二维矩阵grid,其中:
- grid[i][j] = 1 表示该单元格被污染
- grid[i][j] = 0 表示该单元格是干净的
2.2 核心算法选择
解决这类连通区域问题,最常用的两种方法是:
深度优先搜索(DFS):
- 递归实现简洁
- 可能面临栈溢出风险(对于极大网格)
- 时间复杂度:O(m×n)
广度优先搜索(BFS):
- 使用队列实现
- 适合大规模数据
- 同样时间复杂度:O(m×n)
2.3 算法优化考虑
在实际实现中,我们需要考虑:
- 是否修改原数组(标记访问过的单元格)
- 如何处理边界条件(网格边缘)
- 如何避免重复计算
3. Java实现详解
3.1 基础DFS实现
class Solution { public int numIslands(char[][] grid) { if (grid == null || grid.length == 0) return 0; int count = 0; for (int i = 0; i < grid.length; i++) { for (int j = 0; j < grid[0].length; j++) { if (grid[i][j] == '1') { dfs(grid, i, j); count++; } } } return count; } private void dfs(char[][] grid, int i, int j) { if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] != '1') { return; } grid[i][j] = '0'; // 标记为已访问 dfs(grid, i + 1, j); dfs(grid, i - 1, j); dfs(grid, i, j + 1); dfs(grid, i, j - 1); } }3.2 BFS实现版本
import java.util.LinkedList; import java.util.Queue; class Solution { public int numIslands(char[][] grid) { if (grid == null || grid.length == 0) return 0; int count = 0; int[][] directions = {{1,0},{-1,0},{0,1},{0,-1}}; for (int i = 0; i < grid.length; i++) { for (int j = 0; j < grid[0].length; j++) { if (grid[i][j] == '1') { count++; Queue<int[]> queue = new LinkedList<>(); queue.add(new int[]{i, j}); grid[i][j] = '0'; while (!queue.isEmpty()) { int[] current = queue.poll(); for (int[] dir : directions) { int x = current[0] + dir[0]; int y = current[1] + dir[1]; if (x >= 0 && y >= 0 && x < grid.length && y < grid[0].length && grid[x][y] == '1') { queue.add(new int[]{x, y}); grid[x][y] = '0'; } } } } } } return count; } }3.3 性能优化技巧
- 方向数组:使用方向数组简化代码
- 边界检查:提前进行边界检查,避免重复判断
- 原地修改:直接修改原数组,节省空间
4. Python实现详解
4.1 Pythonic实现
def numIslands(grid): if not grid: return 0 count = 0 for i in range(len(grid)): for j in range(len(grid[0])): if grid[i][j] == '1': dfs(grid, i, j) count += 1 return count def dfs(grid, i, j): if i<0 or j<0 or i>=len(grid) or j>=len(grid[0]) or grid[i][j] != '1': return grid[i][j] = '#' dfs(grid, i+1, j) dfs(grid, i-1, j) dfs(grid, i, j+1) dfs(grid, i, j-1)4.2 使用集合记录访问
def numIslands(grid): if not grid: return 0 rows, cols = len(grid), len(grid[0]) visited = set() island_count = 0 def bfs(r, c): queue = collections.deque() visited.add((r, c)) queue.append((r, c)) while queue: row, col = queue.popleft() directions = [[1,0],[-1,0],[0,1],[0,-1]] for dr, dc in directions: r, c = row + dr, col + dc if (r in range(rows) and c in range(cols) and grid[r][c] == "1" and (r, c) not in visited): queue.append((r, c)) visited.add((r, c)) for r in range(rows): for c in range(cols): if grid[r][c] == "1" and (r, c) not in visited: bfs(r, c) island_count += 1 return island_count4.3 Python实现注意事项
- 列表边界:Python的负索引是合法的,需要特别注意
- 递归深度:Python默认递归深度有限,大网格可能需调整
- 集合性能:使用集合比列表更快判断元素是否存在
5. JavaScript实现详解
5.1 ES6实现
const numIslands = (grid) => { if (!grid || grid.length === 0) return 0; let count = 0; const rows = grid.length; const cols = grid[0].length; const dfs = (i, j) => { if (i < 0 || j < 0 || i >= rows || j >= cols || grid[i][j] !== '1') { return; } grid[i][j] = '0'; // 标记为已访问 dfs(i + 1, j); dfs(i - 1, j); dfs(i, j + 1); dfs(i, j - 1); }; for (let i = 0; i < rows; i++) { for (let j = 0; j < cols; j++) { if (grid[i][j] === '1') { dfs(i, j); count++; } } } return count; };5.2 使用队列的BFS实现
const numIslands = (grid) => { if (!grid || grid.length === 0) return 0; let count = 0; const rows = grid.length; const cols = grid[0].length; const directions = [[1,0], [-1,0], [0,1], [0,-1]]; for (let i = 0; i < rows; i++) { for (let j = 0; j < cols; j++) { if (grid[i][j] === '1') { count++; const queue = [[i, j]]; grid[i][j] = '0'; while (queue.length > 0) { const [r, c] = queue.shift(); for (const [dr, dc] of directions) { const newR = r + dr; const newC = c + dc; if (newR >= 0 && newC >= 0 && newR < rows && newC < cols && grid[newR][newC] === '1') { queue.push([newR, newC]); grid[newR][newC] = '0'; } } } } } } return count; };5.3 JS实现注意事项
- 严格相等:使用===而非==
- 队列性能:shift()操作是O(n),大规模数据可优化
- 箭头函数:保持上下文一致
6. C语言实现详解
6.1 基础DFS实现
void dfs(char** grid, int gridSize, int* gridColSize, int i, int j) { if (i < 0 || j < 0 || i >= gridSize || j >= *gridColSize || grid[i][j] != '1') { return; } grid[i][j] = '0'; dfs(grid, gridSize, gridColSize, i + 1, j); dfs(grid, gridSize, gridColSize, i - 1, j); dfs(grid, gridSize, gridColSize, i, j + 1); dfs(grid, gridSize, gridColSize, i, j - 1); } int numIslands(char** grid, int gridSize, int* gridColSize) { if (grid == NULL || gridSize == 0) return 0; int count = 0; for (int i = 0; i < gridSize; i++) { for (int j = 0; j < *gridColSize; j++) { if (grid[i][j] == '1') { dfs(grid, gridSize, gridColSize, i, j); count++; } } } return count; }6.2 使用队列的BFS实现
#include <stdlib.h> typedef struct { int x; int y; } Point; int numIslands(char** grid, int gridSize, int* gridColSize) { if (grid == NULL || gridSize == 0) return 0; int count = 0; int directions[4][2] = {{1,0}, {-1,0}, {0,1}, {0,-1}}; int colSize = *gridColSize; for (int i = 0; i < gridSize; i++) { for (int j = 0; j < colSize; j++) { if (grid[i][j] == '1') { count++; Point* queue = malloc(gridSize * colSize * sizeof(Point)); int front = 0, rear = 0; queue[rear].x = i; queue[rear].y = j; rear++; grid[i][j] = '0'; while (front < rear) { Point current = queue[front++]; for (int k = 0; k < 4; k++) { int x = current.x + directions[k][0]; int y = current.y + directions[k][1]; if (x >= 0 && y >= 0 && x < gridSize && y < colSize && grid[x][y] == '1') { queue[rear].x = x; queue[rear].y = y; rear++; grid[x][y] = '0'; } } } free(queue); } } } return count; }6.3 C语言实现注意事项
- 内存管理:手动管理队列内存
- 指针使用:正确处理二维数组指针
- 边界检查:严格检查数组边界
7. 算法优化与变种问题
7.1 并查集(Union-Find)解法
并查集是解决连通性问题的另一种高效方法:
class UnionFind: def __init__(self, grid): rows, cols = len(grid), len(grid[0]) self.count = 0 self.parent = [i for i in range(rows * cols)] self.rank = [0] * (rows * cols) for i in range(rows): for j in range(cols): if grid[i][j] == '1': self.count += 1 def find(self, i): if self.parent[i] != i: self.parent[i] = self.find(self.parent[i]) return self.parent[i] def union(self, x, y): rootx = self.find(x) rooty = self.find(y) if rootx != rooty: if self.rank[rootx] > self.rank[rooty]: self.parent[rooty] = rootx elif self.rank[rootx] < self.rank[rooty]: self.parent[rootx] = rooty else: self.parent[rooty] = rootx self.rank[rootx] += 1 self.count -= 1 def numIslands(grid): if not grid: return 0 rows, cols = len(grid), len(grid[0]) uf = UnionFind(grid) for i in range(rows): for j in range(cols): if grid[i][j] == '1': grid[i][j] = '0' for x, y in [(i-1,j), (i+1,j), (i,j-1), (i,j+1)]: if 0 <= x < rows and 0 <= y < cols and grid[x][y] == '1': uf.union(i * cols + j, x * cols + y) return uf.count7.2 变种问题
- 统计岛屿周长:计算所有岛屿的周长总和
- 最大岛屿面积:找出最大的连通区域
- 封闭岛屿数量:完全被水域包围的岛屿
- 不同形状岛屿:识别不同形状的岛屿
8. 性能分析与比较
8.1 时间复杂度
所有实现的时间复杂度都是O(m×n),其中m和n是网格的行数和列数。因为每个单元格最多被访问一次。
8.2 空间复杂度
- DFS:O(m×n)(递归栈)
- BFS:O(min(m,n))(队列大小)
- Union-Find:O(m×n)(存储父节点)
8.3 实际性能比较
| 方法 | 语言 | 平均运行时间 | 内存使用 |
|---|---|---|---|
| DFS | Java | 3ms | 40MB |
| BFS | Java | 4ms | 42MB |
| DFS | Python | 120ms | 15MB |
| BFS | Python | 140ms | 16MB |
| Union-Find | Python | 200ms | 20MB |
9. 常见错误与调试技巧
9.1 常见错误
- 无限递归:忘记标记已访问的单元格
- 边界检查错误:数组越界访问
- 类型混淆:字符'1'与数字1混淆
- 空输入处理:未检查输入是否为空
9.2 调试技巧
- 打印网格状态:在每次修改后打印网格
- 小规模测试:先用2x2或3x3网格测试
- 边界测试:测试全1、全0、单行、单列等情况
- 性能分析:使用大网格测试内存和速度
10. 实际应用场景
污染水域算法在实际中有多种应用:
- 图像处理:识别连通区域
- 游戏开发:地图区域划分
- 社交网络:查找社交群体
- 电路设计:检查电路连通性
- 地理信息系统:分析地理特征
11. 面试准备建议
11.1 常见面试问题
- 解释DFS和BFS的区别
- 如何处理极大网格(避免栈溢出)
- 如何优化空间复杂度
- 如何修改算法计算岛屿周长
11.2 回答技巧
- 清晰表达思路:先解释整体方法,再讨论细节
- 考虑边界条件:主动讨论输入验证
- 比较不同方法:展示对多种解法的理解
- 代码风格:使用有意义的变量名,添加必要注释
12. 扩展学习资源
LeetCode相关题目:
- Number of Islands (原题)
- Max Area of Island
- Island Perimeter
- Number of Closed Islands
算法书籍推荐:
- 《算法导论》图算法章节
- 《编程珠玑》中的算法设计技巧
- 《算法图解》中的广度优先搜索介绍
在线课程:
- Coursera上的算法专项课程
- LeetCode探索卡片中的图算法部分
- 各大高校的算法公开课