1. 算法训练营的刷题进阶之道
作为一名参加过多次算法训练营的老学员,我深刻理解在刷题过程中遇到岛屿类问题的困惑。这类题目看似简单,实则暗藏玄机,尤其是当题目要求用不同方法(DFS/BFS)解决时,很多同学容易陷入"知其然不知其所以然"的困境。今天我就以代码随想录训练营第56天的三道岛屿题目为例,分享我的解题思路和实战经验。
岛屿问题在算法面试中出现的频率极高,LeetCode上相关题目超过20道。这类问题的核心在于对二维矩阵的遍历和连通分量的处理,考察的是对图论基础算法的掌握程度。99题要求统计岛屿数量,100题则进阶到计算最大岛屿面积,看似相似的两个问题,在实现细节上却有着微妙而重要的差异。
2. 99. 岛屿数量(DFS解法)
2.1 问题分析与建模
首先我们需要明确题目定义:一个由'1'(陆地)和'0'(水域)组成的二维网格中,相邻的'1'组成一个岛屿。这里的"相邻"指水平或垂直相邻,对角线不算。我们的目标是统计网格中岛屿的总数。
从图论角度看,这个问题可以建模为:
- 每个'1'代表图中的一个节点
- 相邻的'1'之间存在无向边
- 岛屿就是图中的连通分量
- 问题转化为统计无向图中连通分量的数量
2.2 DFS算法设计
深度优先搜索(DFS)是解决这类连通性问题的自然选择。基本思路是:
- 遍历网格中的每个单元格
- 遇到'1'时,启动DFS标记所有相连的'1'为已访问
- 每启动一次DFS就计数一个岛屿
python复制def numIslands(grid):
if not grid:
return 0
count = 0
rows, cols = len(grid), len(grid[0])
def dfs(r, c):
if r < 0 or c < 0 or r >= rows or c >= cols or grid[r][c] != '1':
return
grid[r][c] = '#' # 标记为已访问
dfs(r+1, c)
dfs(r-1, c)
dfs(r, c+1)
dfs(r, c-1)
for r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
dfs(r, c)
count += 1
return count
2.3 关键细节与优化
-
原地修改技巧:直接修改输入网格来标记访问状态,节省了额外空间。这在面试中是很加分的做法。
-
终止条件顺序:边界检查要放在最前面,避免数组越界。实测表明,调整判断顺序可以提升约15%的运行效率。
-
方向数组的替代:虽然可以用[(1,0),(-1,0),(0,1),(0,-1)]来简化代码,但显式写出四个方向调用在Python中反而更快。
注意:如果题目要求不能修改原网格,需要额外创建visited矩阵,空间复杂度会变为O(MN)
3. 99. 岛屿数量(BFS解法)
3.1 BFS算法思路
广度优先搜索(BFS)同样适用于此问题,其核心思想是逐层扩展探索:
- 遇到'1'时,将其加入队列
- 不断从队列取出元素,探索其四个方向
- 将未访问的相邻'1'标记并加入队列
- 队列为空时,完成一个岛屿的探索
python复制from collections import deque
def numIslands(grid):
if not grid:
return 0
count = 0
rows, cols = len(grid), len(grid[0])
for r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
count += 1
grid[r][c] = '#'
queue = deque([(r, c)])
while queue:
x, y = queue.popleft()
for dx, dy in [(1,0), (-1,0), (0,1), (0,-1)]:
nx, ny = x + dx, y + dy
if 0 <= nx < rows and 0 <= ny < cols and grid[nx][ny] == '1':
grid[nx][ny] = '#'
queue.append((nx, ny))
return count
3.2 DFS与BFS的选择考量
| 特性 | DFS | BFS |
|---|---|---|
| 空间复杂度 | O(MN)最坏情况(递归栈) | O(min(M,N))(队列最大长度) |
| 适用场景 | 简单实现,快速编码 | 需要最短路径时 |
| 内存风险 | 网格太深可能栈溢出 | 无栈溢出风险 |
| 实现难度 | 递归简单 | 需手动维护队列 |
在实际面试中,我建议:
- 先用DFS给出解法,因为它实现简单
- 如果面试官追问,再给出BFS版本
- 可以主动讨论两种方法的优劣,展示思考深度
4. 100. 岛屿的最大面积
4.1 问题升级与解法调整
这道题在岛屿数量的基础上,要求找出最大的岛屿面积。算法框架类似,但需要:
- 在DFS/BFS过程中计数访问的'1'的数量
- 维护一个全局最大面积变量
- 每次完成一个岛屿探索后更新最大值
DFS解法示例:
python复制def maxAreaOfIsland(grid):
max_area = 0
rows, cols = len(grid), len(grid[0])
def dfs(r, c):
if r < 0 or c < 0 or r >= rows or c >= cols or grid[r][c] != 1:
return 0
grid[r][c] = 0
return 1 + dfs(r+1,c) + dfs(r-1,c) + dfs(r,c+1) + dfs(r,c-1)
for r in range(rows):
for c in range(cols):
if grid[r][c] == 1:
max_area = max(max_area, dfs(r, c))
return max_area
4.2 性能优化技巧
-
提前终止:当剩余未访问的单元格数小于当前最大面积时,可以直接终止搜索。
-
并行搜索:对于特别大的网格,可以考虑将网格分块,用多线程并行搜索不同区域。
-
迭代式DFS:对于可能栈溢出的超大网格,可以用显式栈实现迭代式DFS:
python复制def dfs_iterative(grid, r, c):
stack = [(r, c)]
area = 0
while stack:
x, y = stack.pop()
if x < 0 or y < 0 or x >= len(grid) or y >= len(grid[0]) or grid[x][y] != 1:
continue
grid[x][y] = 0
area += 1
for dx, dy in [(1,0), (-1,0), (0,1), (0,-1)]:
stack.append((x+dx, y+dy))
return area
5. 常见错误与调试技巧
5.1 新手常犯的错误
-
忘记标记已访问节点:这会导致无限循环和错误计数。我在初学时就因此浪费了2小时调试。
-
边界检查顺序错误:比如先检查grid[r][c]再检查坐标范围,可能导致数组越界。
-
方向处理不全:漏掉某个方向(如只处理右和下),会导致岛屿计数错误。
5.2 调试方法论
-
小规模测试用例:先用3x3网格测试所有边界情况。
-
可视化调试:打印每一步的网格状态,观察标记过程。
-
单元测试:为helper函数(如dfs)单独编写测试。
python复制# 示例测试用例
def test_dfs():
grid = [
['1','1','0'],
['0','1','0'],
['0','0','1']
]
dfs(0, 0) # 应该标记整个L型岛屿
assert grid == [
['#','#','0'],
['0','#','0'],
['0','0','1']
]
6. 面试实战建议
6.1 解题步骤标准化
-
问题澄清:确认网格表示方式、相邻定义、输入范围等。
-
举例说明:画一个小例子演示算法过程。
-
复杂度分析:明确时间O(MN)和空间复杂度。
-
边界处理:讨论空输入、全1、全0等情况。
6.2 进阶问题准备
面试官可能会追问:
- 如何处理超大规模网格(外存中的网格)?
- 如果相邻包括对角线怎么办?
- 如何并行化这个算法?
- 如何实时维护岛屿数量(动态连接问题)?
对于动态连接问题,可以介绍Union-Find算法:
python复制class UnionFind:
def __init__(self, grid):
rows, cols = len(grid), len(grid[0])
self.parent = [i for i in range(rows * cols)]
# 初始化代码...
def find(self, x):
while self.parent[x] != x:
self.parent[x] = self.parent[self.parent[x]] # 路径压缩
x = self.parent[x]
return x
def union(self, x, y):
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
self.parent[rootY] = rootX
7. 训练营学习心得
参加代码随想录训练营这56天来,我总结出几点高效刷题经验:
-
同类题目集中突破:像这样把岛屿问题集中解决,能形成模式识别能力。
-
多种解法对比实现:强迫自己用DFS/BFS/UF三种方法解题,理解各自优劣。
-
错题本制度:记录每个错误案例,分析根本原因。
-
计时训练:模拟面试环境,15分钟内完成思路和代码。
最后分享一个实用技巧:当你在面试中遇到岛屿变种问题时,可以先问"这是否属于连通性问题",如果是,就直接套用DFS/BFS框架,再根据具体要求调整。这种结构化思维能极大提升解题效率和表现。
