

亲爱的同学们,大家好!今天我们要一起探索一个非常经典且在面试中高频出现的算法问题——岛屿数量。这个问题不仅能够很好地检验我们对图论搜索算法的理解,还能锻炼我们的编程思维!✨
想象一下,你是一名探险家,手持一张由’0’(水)和’1’(陆地)组成的藏宝图。你的任务是数出这张图上有多少个岛屿(被水完全包围的陆地)。听起来简单,对吧?但当地图变得复杂,岛屿形状各异时,如何高效地完成这个任务呢?这就是我们今天要解决的问题!🧭
这个问题不仅是算法面试中的常客,更是理解深度优先搜索(DFS)和广度优先搜索(BFS)的绝佳案例。掌握了它,你将能够应对各种图论搜索问题,为你的算法之旅打下坚实基础。
让我们一起揭开"岛屿数量"这个经典问题的神秘面纱吧!👀
在深入问题之前,我们先来了解一些基础知识:
"岛屿数量"问题通常描述为:
给你一个由 '1'(陆地)和 '0'(水)组成的二维网格 grid,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
例如:
输入:grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
输出:1输入:grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
输出:3深度优先搜索是一种用于遍历或搜索树或图的算法。它沿着树的深度遍历树的节点,尽可能深地搜索树的分支。当节点v的所有边都已被探寻过,搜索将回溯到发现节点v的那条边的起始节点。
DFS的基本思想是:
在实现上,DFS通常使用递归或栈来实现。
广度优先搜索是一种用于遍历或搜索树或图的算法。与DFS不同,BFS从起始节点开始,先访问起始节点的所有邻接节点,然后再访问这些邻接节点的邻接节点,以此类推。
BFS的基本思想是:
在实现上,BFS通常使用队列来实现。
在网格问题中,我们通常将每个单元格视为一个节点,相邻的单元格之间有边相连。在"岛屿数量"问题中:
解决"岛屿数量"问题有两种主要方法:DFS和BFS。让我们一一解析:
DFS的思路是:
这种方法的关键在于:每次发现一个新的陆地,我们就将与之相连的整个岛屿都标记为已访问,这样就不会重复计数。
BFS的思路与DFS类似,但使用队列而不是递归或栈:
本题的难点在于:
下面我们将重点讲解DFS和BFS两种实现方法。
/**
* 岛屿数量 - DFS实现
*/
public class NumberOfIslandsDFS {
/**
* 计算岛屿数量
* @param grid 二维字符网格,'1'表示陆地,'0'表示水
* @return 岛屿的数量
*/
public static int numIslands(char[][] grid) {
// 边界条件检查
if (grid == null || grid.length == 0 || grid[0].length == 0) {
return 0;
}
int rows = grid.length;
int cols = grid[0].length;
int count = 0;
// 遍历整个网格
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
// 如果当前单元格是陆地
if (grid[i][j] == '1') {
// 岛屿数量加1
count++;
// 使用DFS将与当前陆地相连的所有陆地标记为已访问
dfs(grid, i, j, rows, cols);
}
}
}
return count;
}
/**
* DFS遍历与当前陆地相连的所有陆地,并标记为已访问
* @param grid 二维字符网格
* @param i 当前行索引
* @param j 当前列索引
* @param rows 网格的行数
* @param cols 网格的列数
*/
private static void dfs(char[][] grid, int i, int j, int rows, int cols) {
// 边界条件检查或当前单元格是水
if (i < 0 || i >= rows || j < 0 || j >= cols || grid[i][j] == '0') {
return;
}
// 将当前陆地标记为已访问(通过将其改为水)
grid[i][j] = '0';
// 递归访问上、下、左、右四个方向的相邻单元格
dfs(grid, i - 1, j, rows, cols); // 上
dfs(grid, i + 1, j, rows, cols); // 下
dfs(grid, i, j - 1, rows, cols); // 左
dfs(grid, i, j + 1, rows, cols); // 右
}
// 测试代码
public static void main(String[] args) {
// 测试用例1
char[][] grid1 = {
{'1', '1', '1', '1', '0'},
{'1', '1', '0', '1', '0'},
{'1', '1', '0', '0', '0'},
{'0', '0', '0', '0', '0'}
};
System.out.println("测试用例1的岛屿数量: " + numIslands(grid1)); // 预期输出: 1
// 测试用例2
char[][] grid2 = {
{'1', '1', '0', '0', '0'},
{'1', '1', '0', '0', '0'},
{'0', '0', '1', '0', '0'},
{'0', '0', '0', '1', '1'}
};
System.out.println("测试用例2的岛屿数量: " + numIslands(grid2)); // 预期输出: 3
}
}import java.util.LinkedList;
import java.util.Queue;
/**
* 岛屿数量 - BFS实现
*/
public class NumberOfIslandsBFS {
/**
* 计算岛屿数量
* @param grid 二维字符网格,'1'表示陆地,'0'表示水
* @return 岛屿的数量
*/
public static int numIslands(char[][] grid) {
// 边界条件检查
if (grid == null || grid.length == 0 || grid[0].length == 0) {
return 0;
}
int rows = grid.length;
int cols = grid[0].length;
int count = 0;
// 遍历整个网格
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
// 如果当前单元格是陆地
if (grid[i][j] == '1') {
// 岛屿数量加1
count++;
// 使用BFS将与当前陆地相连的所有陆地标记为已访问
bfs(grid, i, j, rows, cols);
}
}
}
return count;
}
/**
* BFS遍历与当前陆地相连的所有陆地,并标记为已访问
* @param grid 二维字符网格
* @param i 当前行索引
* @param j 当前列索引
* @param rows 网格的行数
* @param cols 网格的列数
*/
private static void bfs(char[][] grid, int i, int j, int rows, int cols) {
// 创建队列,用于BFS
Queue<int[]> queue = new LinkedList<>();
// 将当前陆地加入队列
queue.offer(new int[]{i, j});
// 将当前陆地标记为已访问
grid[i][j] = '0';
// 定义四个方向的偏移量:上、下、左、右
int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
// BFS遍历
while (!queue.isEmpty()) {
int[] current = queue.poll();
int row = current[0];
int col = current[1];
// 检查四个方向的相邻单元格
for (int[] dir : directions) {
int newRow = row + dir[0];
int newCol = col + dir[1];
// 检查边界条件和是否是陆地
if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols && grid[newRow][newCol] == '1') {
// 将相邻的陆地加入队列
queue.offer(new int[]{newRow, newCol});
// 将相邻的陆地标记为已访问
grid[newRow][newCol] = '0';
}
}
}
}
// 测试代码
public static void main(String[] args) {
// 测试用例1
char[][] grid1 = {
{'1', '1', '1', '1', '0'},
{'1', '1', '0', '1', '0'},
{'1', '1', '0', '0', '0'},
{'0', '0', '0', '0', '0'}
};
System.out.println("测试用例1的岛屿数量: " + numIslands(grid1)); // 预期输出: 1
// 测试用例2
char[][] grid2 = {
{'1', '1', '0', '0', '0'},
{'1', '1', '0', '0', '0'},
{'0', '0', '1', '0', '0'},
{'0', '0', '0', '1', '1'}
};
System.out.println("测试用例2的岛屿数量: " + numIslands(grid2)); // 预期输出: 3
}
}numIslands:主函数,遍历网格并计数岛屿dfs:递归函数,标记与当前陆地相连的所有陆地为已访问学习"岛屿数量"这个问题对Java初学者有着多方面的重要意义:
这个问题是图论搜索算法的经典应用,通过它可以理解DFS和BFS这两种基本的图遍历算法。这些算法在计算机科学中有广泛的应用,是算法学习的基础。🧭
DFS实现中的递归调用是理解递归思想的绝佳例子。递归是解决许多复杂问题的强大工具,掌握它对于提升编程能力至关重要。🔄
BFS实现中使用了队列这一基本数据结构,这有助于理解如何在实际问题中应用数据结构。选择合适的数据结构是解决算法问题的关键。📊
这个问题涉及到二维数组的遍历和操作,这是Java编程中的基本技能。熟练掌握二维数组的操作对于解决矩阵、图像处理等问题非常重要。🔢
"岛屿数量"是技术面试中的常见题目,掌握它不仅能提高你的编程能力,还能增加你在面试中的竞争力。特别是当你能够同时解释DFS和BFS两种解法时,会给面试官留下深刻印象。💼
这个算法在实际开发中有很多应用场景,如:
学习这个算法有助于你在实际工作中解决类似问题。🌐
今天我们一起学习了"岛屿数量"这个经典算法问题。我们不仅了解了它的基本概念和实现方法,还探讨了DFS和BFS两种解法的异同。
通过这个问题,我们学到了以下几点:
这个问题虽然看起来简单,但它包含了图论算法的精髓。掌握了这个问题,你就掌握了DFS和BFS的基本应用,为学习更复杂的图算法打下了坚实基础。
希望通过这篇文章,你不仅学会了解决"岛屿数量"问题的方法,更重要的是理解了背后的算法思想。在编程的道路上,这些思想和技巧将会一直伴随着你,帮助你解决各种各样的挑战。💪
记住,算法学习是一个循序渐进的过程,今天的每一步都是为了明天的飞跃做准备。希望你能将今天学到的知识应用到实际问题中,不断提升自己的编程能力!🌈
如果你对这个问题还有任何疑问,或者想了解更多相关的算法和数据结构,欢迎在评论区留言交流!我们下次再见!👋
学习提示:尝试解决"岛屿的最大面积"问题,该问题要求计算网格中最大的岛屿面积。这将帮助你更深入地理解DFS和BFS在网格问题中的应用!