岛屿问题是dfs回溯算法中的经典问题,以下就是对岛屿问题相关的问题进行汇总。
文章目录
- 岛屿问题
- 463. 岛屿的周长
- 200. 岛屿数量
- 305. 岛屿数量Ⅱ
- 695. 岛屿的最大面积
- 1254. 统计封闭岛屿的数目
- 827. 最大人工岛
岛屿问题
463. 岛屿的周长
1.题目描述
leetcode题目链接:463. 岛屿的周长
2.思路分析
用len表示周长,顺序遍历二维矩阵,当遇到岛屿即grid[i][j] == 1时,执行递归
- 如果为陆地
grid[i][j] == 1
时,令grid[i][j] == -1
,然后递归遍历四个方向 - 遇到边界、或者水域
grid[i][j] == 0
时len+1(这样只会算最外围的周长)
3.参考代码
class Solution {
public int len = 0;
public int islandPerimeter(int[][] grid) {
int m = grid.length, n = grid[0].length;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
dfs(grid, i, j);
}
}
}
return len;
}
public void dfs (int[][] grid, int i, int j) {
int m = grid.length, n = grid[0].length;
if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == 0) {
len++;
} else if (grid[i][j] == 1){
grid[i][j] = -1;
dfs(grid, i + 1, j);
dfs(grid, i - 1, j);
dfs(grid, i, j + 1);
dfs(grid, i, j - 1);
}
}
}
200. 岛屿数量
1.题目描述
leetcode题目链接:200. 岛屿数量
2.思路分析
方法一:DFS
顺序遍历,若为岛屿 grid[i][j] == 1
,则res++,然后将与此位置岛屿相连的陆地置为1,
dfs()思路如下
- 若遇到边界或者水域,终止
- 令陆地变为水域grid[i][j] == 0,然后递归遍历四个方向
方法二:并查集
初始时将每一个“1”的格子看作一个岛。然后遍历整个格,考察该格子右侧和下侧的格子,如果也是“1”,将其合并到当前格子所在的岛中,每次合并都累计合并次数。岛屿数量就是最初单个格子岛屿数量减去合并次数。
需要注意初始化时要将二维grid[i][j]映射到一维id[k]中,k = i * n + j,n是grid的列数。初始化id: grid[i][j] = 1时 id[k] = k,sz[k] = 1
。
3.参考代码
class Solution {
public int numIslands(char[][] grid) {
int m = grid.length, n = grid[0].length;
int res = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '1') {
res++;
dfs(grid, i, j);
}
}
}
return res;
}
public void dfs(char[][] grid, int i, int j) {
int m = grid.length, n = grid[0].length;
if (i < 0 || j < 0 || i >= m || j >= n || grid[i][j] == '0') {
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);
}
}
方法二:并查集
class Solution {
int landCount = 0;
int mergeCount = 0;
public int numIslands(char[][] grid) {
// 并查集
int m = grid.length, n = grid[0].length;
UF uf = new UF(grid);
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
int land = i * n + j;
if (grid[i][j] == '1') {
if (j < n - 1 && grid[i][j + 1] == '1') { // 右侧
uf.union(land, land + 1);
}
if (i < m - 1 && grid[i + 1][j] == '1') { // 下侧
uf.union(land, land + n);
}
}
}
}
return landCount - mergeCount;
}
class UF { // 路径压缩的加权quick-union算法模板
int[] id;
int[] sz;
private UF (char[][] grid) {
int m = grid.length, n = grid[0].length;
id = new int[m * n];
sz = new int[m * n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '1') { // 累计单个格子岛屿数量
landCount++;
int k = i * n + j;
id[k] = k;
sz[k] = 1;
}
}
}
}
public void union (int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot != qRoot) {
mergeCount++; // 不属于一个集合时,合并次数加1
if (sz[pRoot] < sz[qRoot]) {
id[pRoot] = qRoot;
sz[qRoot] += sz[pRoot];
} else {
id[qRoot] = pRoot;
sz[pRoot] += sz[qRoot];
}
}
}
private int find (int p) {
if (p == id[p]) {
return p;
}
id[p] = find(id[p]);
return id[p];
}
}
}
305. 岛屿数量Ⅱ
1.题目描述
leetcode题目链接:plus会员题目,可以直接看题目描述。
输入: m = 3, n = 3,
positions = [[0,0], [0,1], [1,2], [2,1]]
输出: [1,1,2,3]
解析:
起初,二维网格 grid 被全部注入「水」。(0 代表「水」,1 代表「陆地」)
0 0 0
0 0 0
0 0 0
操作 #1:addLand(0, 0) 将 grid[0][0] 的水变为陆地。
1 0 0
0 0 0 Number of islands = 1
0 0 0
操作 #2:addLand(0, 1) 将 grid[0][1] 的水变为陆地。
1 1 0
0 0 0 岛屿的数量为 1
0 0 0
操作 #3:addLand(1, 2) 将 grid[1][2] 的水变为陆地。
1 1 0
0 0 1 岛屿的数量为 2
0 0 0
操作 #4:addLand(2, 1) 将 grid[2][1] 的水变为陆地。
1 1 0
0 0 1 岛屿的数量为 3
0 1 0
2.思路分析
方法一:暴力搜索
每进行一次操作,全图遍历一次,调用岛屿数量的代码即可。
方法二:并查集
不使用路径压缩的加权的并查集模板,在函数内部初始化及合并。
3.参考代码
方法一:暴力搜索
class Solution {
public int[] numIslands2(int m, int n, int[][] positions) {
int[] res = new int[positions.length()];
char[][] grid = new int[m][n];
for(int i = 0; i < positions.length; i++) {
int e = positions[i];
grid[e[0]][e[1]] = '1';
//此处调用岛屿数量1中函数
res[i] = numIslands(grid);
}
return res;
}
}
方法二:并查集
class Solution {
public int[] numIslands2(int m, int n, int[][] positions) {
int[] res = new int[positions.length];
int[] id = new int[m * n]; //将二维矩阵压缩为一维父链接数组
int[] dirs = {0, -1, 0, 1, 0}; //将二维方向压缩为一维
int num = 0; //初始岛屿数为0
char[][] grid = new char[m][n];
for(int i = 0; i < positions.length; i++) {
int[] e = positions[i];
//初始化
grid[e[0]][e[1]] = '1';
int p = e[0] * n + e[1];
id[p] = p;
num += 1;
//遍历上下左右四个方向,若为岛屿则合并
for(int k = 0; k < dirs.length - 1; k++) {
int x = e[0] + dirs[k];
int y = e[1] + dirs[k + 1];
if(x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == '1') {
int q = x * n + y;
int qroot = find(id, q);
//合并
id[qroot] = p;
//岛屿数量减一
num -= 1;
}
}
res[i] = num;
}
return res;
}
private int find(int[] id, int p) {
if(p == id[p]) return p;
return id[p];
}
}
695. 岛屿的最大面积
1.题目描述
leetcode题目链接:695. 岛屿的最大面积
2.思路分析
与岛屿数量一样,在将岛屿置为0时,统计岛屿的面积。然后更新最大面积。本题可参考华为机试:华为机试:最大岛屿体积。
3.参考代码
class Solution {
int count;
public int maxAreaOfIsland(int[][] grid) {
int m = grid.length, n = grid[0].length;
int res = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
count = 0;
dfs(grid, i , j);
res = Math.max(res, count);
}
}
}
return res;
}
public void dfs(int[][] grid, int i, int j) {
int m = grid.length, n = grid[0].length;
if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == 0) {
return ;
}
count++;
grid[i][j] = 0;
dfs(grid, i + 1, j);
dfs(grid, i - 1, j);
dfs(grid, i, j + 1);
dfs(grid, i, j - 1);
}
}
1254. 统计封闭岛屿的数目
1.题目描述
leetcode题目链接:1254. 统计封闭岛屿的数目
2.思路分析
与岛屿数量不同的的是,这道题边界的岛屿不算岛屿。周围都为水域时才算岛屿。
陆地越过边界条件则不是岛屿,返回false
if (i < 0 || i >= m || j < 0 || j >= n) {
return false;
}
陆地边界遇到水域,返回true
if (grid[i][j] == 1) {
return true;
}
需要注意的是,dfs上下左右的时候有两种写法:
boolean top = dfs(grid, i, j - 1);
boolean bottom = dfs(grid, i, j + 1);
boolean left = dfs(grid, i - 1, j);
boolean right = dfs(grid, i + 1, j);
return top && bottom && left && right;
或者:
return dfs(grid, i, j - 1) & dfs(grid, i, j + 1) & dfs(grid, i - 1, j) & dfs(grid, i + 1, j);
注意:这里不能直接返回&&相连的。
- Java 中 && 和 & 都是表示与的逻辑运算符,都表示逻辑运输符 and,当两边都为 true 的时候,整个运算结果才为 true ,否则为 false 。
- && :有短路功能,当第⼀个表达式的值为 false 的时候,则不再计算第⼆个表达式。
- & :不管第⼀个表达式结果是否为 true ,第⼆个都会执⾏。
我们的目的是让上下左右都dfs过以后再判断。
方法二:先去除与边界相连的岛屿,再统计岛屿数量。
// 先去除与边界相连岛屿
for (int i = 0; i < m; i++) { //遍历第一列和最后一列
dfs(grid, i, 0);
dfs(grid, i, n - 1);
}
for (int j = 0; j < n; j++) { //遍历第一行和最后一行
dfs(grid, 0, j);
dfs(grid, m - 1, j);
}
统计岛屿数量则与之前代码相同。
3.参考代码
class Solution {
public int closedIsland(int[][] grid) {
// 与岛屿数量相同,只是不考虑与边界相连
int m = grid.length, n = grid[0].length;
int res = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 0 && dfs(grid, i, j)) {
res++;
}
}
}
return res;
}
public boolean dfs(int[][] grid, int i, int j) {
int m = grid.length, n = grid[0].length;
if (i < 0 || i >= m || j < 0 || j >= n) {
return false;
}
if (grid[i][j] == 1) {
return true;
}
grid[i][j] = 1;
boolean top = dfs(grid, i, j - 1);
boolean bottom = dfs(grid, i, j + 1);
boolean left = dfs(grid, i - 1, j);
boolean right = dfs(grid, i + 1, j);
return top && bottom && left && right;
// return dfs(grid, i, j - 1) & dfs(grid, i, j + 1) & dfs(grid, i - 1, j) & dfs(grid, i + 1, j);
}
}
先去除与边界相连的岛屿,再统计岛屿数量
class Solution {
public int closedIsland(int[][] grid) {
// 与岛屿数量相同,只是不考虑与边界相连
// 先去除与边界相连的岛屿,再统计岛屿的数量
int m = grid.length, n = grid[0].length;
// 先去除与边界相连岛屿
for (int i = 0; i < m; i++) { //遍历第一列和最后一列
dfs(grid, i, 0);
dfs(grid, i, n - 1);
}
for (int j = 0; j < n; j++) { //遍历第一行和最后一行
dfs(grid, 0, j);
dfs(grid, m - 1, j);
}
// 再统计岛屿数量
int res = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 0) {
res++;
dfs(grid, i, j);
}
}
}
return res;
}
public void dfs(int[][] grid, int i, int j) {
int m = grid.length, n = grid[0].length;
if (i < 0 || j < 0 || i >= m || j >= n || grid[i][j] == 1) {
return;
}
grid[i][j] = 1;
dfs(grid, i + 1, j);
dfs(grid, i - 1, j);
dfs(grid, i, j + 1);
dfs(grid, i, j - 1);
}
}
827. 最大人工岛
1.题目描述
leetcode题目链接:827. 最大人工岛
2.思路分析
对网格做两遍 DFS:
- 第一遍 DFS 遍历陆地格子,计算每个岛屿的面积并标记岛屿;
- 第二遍 DFS 遍历海洋格子,观察每个海洋格子相邻的岛屿格子。重点!!!把每个岛屿格子的值标为面积
0是海洋1是岛屿
0是海洋,2,3,4,5,6是岛屿的序号
0是海洋,其他数字的岛屿的面积
岛屿序号和面积的对应关系,即代码中的hashmap:
3.参考代码
class Solution {
public int largestIsland(int[][] grid) {
int n = grid.length;
if (n == 0) return 1;
int res = 0;
int index = 2; // index表示岛屿的编号,0是海洋1是陆地,从2开始遍历
HashMap<Integer,Integer> indexAndAreas = new HashMap<>(); // 岛屿编号:岛屿面积
/**
* 计算每个岛屿的面积,并标记是第几个岛屿
*/
for (int i = 0; i < n; i++){
for (int j = 0; j < n; j++){
if (grid[i][j] == 1){ // 遍历没有访问过的岛屿格子
int area = dfs(grid, i, j, index); // 返回每个岛屿的面积,dfs
indexAndAreas.put(index,area); // 存入岛屿编号、岛屿面积
index++; // 岛屿编号增加
res = Math.max(res,area); // 记录最大的岛屿面积
}
}
}
if (res == 0) return 1; // res=0 表示没有陆地,那么造一块,则返回1即可
/**
* 遍历海洋格子,假设这个格子填充,那么就把上下左右是陆地的格子所在的岛屿连接起来
*/
for (int i = 0; i < n; i++){
for (int j = 0; j < n; j++){
if (grid[i][j] == 0){ // 遍历海洋格子
HashSet<Integer> hashSet = findNeighbour(grid, i, j); // 把上下左右邻居放入set去重
if (hashSet.size() < 1) continue; // 如果海洋格子周围没有格子不必计算
int twoIsland = 1; // 填充这个格子,初始为1,这个变量记录合并岛屿后的面积
for (Integer key: hashSet){
twoIsland += indexAndAreas.get(key);//该格子填充,则上下左右的陆地的都连接了,通过序号获得面积,加上面积
}
res = Math.max(res,twoIsland);//比较得到最大的面积
}
}
}
return res;
}
/**
* 对于海洋格子,找到上下左右
* 每个方向,都要确保有效inArea以及是陆地格子,则表示是该海洋格子的陆地邻居
*/
private HashSet<Integer> findNeighbour(int[][] grid, int i, int j){
HashSet<Integer> hashSet = new HashSet<>();
if (inArea(grid, i - 1, j) && grid[i - 1][j] != 0){
hashSet.add(grid[i - 1][j]);
}
if (inArea(grid, i + 1, j) && grid[i + 1][j] != 0){
hashSet.add(grid[i + 1][j]);
}
if (inArea(grid, i, j - 1) && grid[i][j - 1] != 0){
hashSet.add(grid[i][j - 1]);
}
if (inArea(grid, i, j + 1) && grid[i][j + 1] != 0){
hashSet.add(grid[i][j + 1]);
}
return hashSet;
}
/**
* dfs方法,将格子填充为index,即表示这个格子属于哪个岛的
* 计算岛屿面积,上下左右,当然这个可以优化的,因为不需要计算上面的,会有重复
*/
private int dfs(int[][] grid, int i, int j, int index){
if (!inArea(grid, i, j)){
return 0;
}
//不为1,表示为海洋格子或者已经遍历过了
if (grid[i][j] != 1){
return 0;
}
grid[i][j] = index;//设置当前格子为某个岛屿编号
return 1 + dfs(grid, i - 1, j, index) + dfs(grid, i + 1, j, index) + dfs(grid, i , j - 1, index) + dfs(grid, i, j + 1, index);
}
/**
* 判断grid[i][j]是否大小合适
*/
private boolean inArea(int[][] grid, int i, int j){
return i >= 0 && i < grid.length && j >= 0 && j < grid[0].length;
}
}
参考: