【算法题解】53. 计封闭岛屿的数目

这是一道 中等难度 的题

https://leetcode.cn/problems/number-of-closed-islands/

题目

二维矩阵 grid 0 0 0(土地)和 1 1 1 (水)组成。岛是由最大的 4 4 4 个方向连通的 0 0 0 组成的群,封闭岛是一个 完全 1 1 1 包围(左、上、右、下)的岛。

请返回 封闭岛屿 的数目。

示例 1:

输入:grid =[
    		[1,1,1,1,1,1,1,0],
            [1,0,0,0,0,1,1,0],
            [1,0,1,0,1,1,1,0],
            [1,0,0,0,0,1,0,1],
            [1,1,1,1,1,1,1,0]
		   ] 
输出:2 
解释: 灰色区域的岛屿是封闭岛屿,因为这座岛屿完全被水域包围(即被 1 区域包围)。

示例 2:

输入:grid = [
    		 [0,0,1,0,0],
    		 [0,1,0,1,0],
    		 [0,1,1,1,0]
			] 
输出:1 

示例 3:

输入:grid = [
    		 [1,1,1,1,1,1,1],              
    		 [1,0,0,0,0,0,1],              
             [1,0,1,1,1,0,1],              
             [1,0,1,0,1,0,1],              
             [1,0,1,1,1,0,1],              
             [1,0,0,0,0,0,1],              
             [1,1,1,1,1,1,1]
            ] 
输出:2 

提示:

  • 1 < = g r i d . l e n g t h 1 <= grid.length 1<=grid.length, g r i d [ 0 ] . l e n g t h < = 100 grid[0].length <= 100 grid[0].length<=100
  • 0 < = g r i d [ i ] [ j ] < = 1 0 <= grid[i][j] <=1 0<=grid[i][j]<=1

题解

求岛屿数量也就是求数值为 0 的连通块。思路为遍历二维数组中的每一个元素,如果遇到 0 ,就说明遇到了岛屿,答案计数加一,然后一定要记得将这个岛屿覆盖到的所有 0 设置为 1 或其他 非0 的值,以免导致重复计数。

还有一点需要注意的是,题目要求的是 封闭岛屿,也就是不能包含挨着边界的外部连通块,所以在求内部连通块个数前可以先将外部连通块清除掉,清除方式同样是将所有 0 设置为 1

如何将岛屿覆盖到的所有 ‘0’ 设置为 ‘1’ 呢?

首先将当前位置 g r i d [ i ] [ j ] grid[i][j] grid[i][j]0 设置为 1,然后再将挨着这个位置的 上下左右 4个位置的 0 设置为 1,重复此步骤,每一步的处理逻辑都是一样的,又是典型的 递归 的思路。

递归函数: 将当前位置 g r i d [ i ] [ j ] grid[i][j] grid[i][j]0 设置为 1,然后再递归上下左右4个位置。

边界条件: 有两种可能,分别是:

  • i i i 或者 j j j 出界,返回。
  • g r i d [ i ] [ j ] = 1 grid[i][j] = 1 grid[i][j]=1,返回。

翻译成代码,以 Java 为例

// 递归函数,i 和 j 为当前位置的坐标
private void resetZero(int[][] grid, int i, int j){

    // 边界条件
    if(i < 0 || i >= grid.length){
        return;
    }
    if(j < 0 || j >= grid[0].length){
        return;
    }
    if(grid[i][j] == 1){
        return;
    }
        
	// 将当前位置设置为1
    grid[i][j] = 1;

    // 递归 上下左右 4个位置
    resetZero(grid, i + 1, j, m, n);
    resetZero(grid, i - 1, j, m, n);
    resetZero(grid, i, j + 1, m, n);
    resetZero(grid, i, j - 1, m, n);
}

综上,总体思路为:

  1. 循环遍历 4 条边,遇到 0 就使用递归函数 r e s e t Z e r o resetZero resetZero 将这个 0 所属于的外部连通块清除掉。
  2. 循环遍历 4 条边以内的所有点,遇到 0 就将答案 ans 加一,并将这个 0 所属于的内部连通块清除掉。
Java 代码实现
class Solution {
    public int closedIsland(int[][] grid) {
        // 先把边上的0和其联通的消除掉
        
        int m = grid.length;
        int n = grid[0].length;
        
        for(int i = 0; i < m; i++){
            // i = 0 或者 i = m - 1 或者 j = 0 或者 j = n - 1 时是边
            int step =  i == 0 || i == m - 1 ? 1 : n - 1;
            for(int j = 0; j < n; j += step){
                resetZero(grid, i, j);
            }
        }

        int ans = 0;
        for(int i = 1; i < m - 1; i++){
            for(int j = 1; j < n - 1; j++){
                // 遇到 0,答案就+1;
                // 然后将这个岛清除掉
                if(grid[i][j] == 0){
                    ans++;
                    resetZero(grid, i, j);
                }
            }
        }
        return ans;
    }

    private void resetZero(int[][] grid, int i, int j){

        if(i < 0 || i >= grid.length){
            return;
        }
        if(j < 0 || j >= grid[0].length){
            return;
        }
        if(grid[i][j] == 1){
            return;
        }
        

        grid[i][j] = 1;
        resetZero(grid, i + 1, j);
        resetZero(grid, i - 1, j);
        resetZero(grid, i, j + 1);
        resetZero(grid, i, j - 1);
    }
}
Go 代码实现
func closedIsland(grid [][]int) int {
    m, n := len(grid), len(grid[0])

    for i := 0; i < m; i++ {
        step := 1
        if i != 0 && i != m - 1 {
            step = n - 1
        }
        for j := 0; j < n; j += step {
            resetZero(grid, i, j)
        }
    }

    ans := 0
    for i := 1; i < m - 1; i++ {
        for j := 1; j < n - 1; j++ {
            if grid[i][j] == 0 {
                ans++
            }
            resetZero(grid, i, j)
        }
    }

    return ans
}

func resetZero(grid [][]int, i int, j int) {
    m, n := len(grid), len(grid[0])
    if i < 0 || i >= m || j < 0 || j >= n {
        return
    }
    if grid[i][j] == 1 {
        return
    }

    grid[i][j] = 1

    resetZero(grid, i + 1, j)
    resetZero(grid, i - 1, j)
    resetZero(grid, i, j - 1)
    resetZero(grid, i, j + 1)

}

复杂度分析

时间复杂度: O ( M N ) O(MN) O(MN)MN 分别为二维矩阵的高度和宽度。矩阵中的每一个为 1 的点都需要遍历 1 次。 每个为 0 的点需要遍历最多两次,一次是将 0 改为 1,一次是按顺序遍历检查是否是 0

空间复杂度: O ( M N ) O(MN) O(MN)MN 分别为二维矩阵的高度和宽度。空间复杂度取决于递归调用栈的深度,最大为 MN,即举矩阵中都是 0 的时候。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

i余数

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值